Subchapter 25.42
references/phases/generate/generate-artifacts-infra.mdMarkdown15 KBView on GitHub
Fragment unit. See
generate.mdfor how it is composed into the phase.
Emits idiomatic replacement Terraform for the designed architecture. Where the customer supplied IaC, its module structure and naming inform the output — that is what the half of discovery was for, and it is why IaC stays a first-class source even when live discovery is authoritative for state.
Per-domain .tf files (compute, data, network, security) are emitted as the design
requires. They are the open tail: this fragment lists in _contributes only what
it writes unconditionally (the five core files); the domain files are governed by
this prose and the phase’s _asserts.
Secrets are never inlined. A Key Vault entry becomes a Secrets Manager reference, and the value stays where it was.
Execute the steps in order. Do not skip or optimize.
Read from $MIGRATION_DIR/:
aws-design.json (REQUIRED) — services[] (per-resource mappings with aws_service,
aws_config, confidence, azure_type), clusters[] (workload grouping + tier),
deferred[], target_region, cpu_architecture.preferences.json (REQUIRED) — design_constraints, data.availability, licensing,
identity, environment/region.estimation-infra.json (REQUIRED) — for the budget limit and the cost-tier README note.azure-resource-inventory.json (REQUIRED) — source config for attribute population and
the module/naming provenance (config.tf_module, config.tf_address).If any REQUIRED file is missing: STOP — “Missing required artifact: [filename].”
Generate $MIGRATION_DIR/terraform/, emitting only the domain files for domains that
have services in aws-design.json:
| File | Domain | Contains |
|---|---|---|
main.tf | core | provider, S3 backend, data sources, cost-tier header |
variables.tf | core | all input variables (types, defaults, placeholder-guard validation) |
outputs.tf | core | key resource outputs + migration_summary |
.gitignore | core | tfstate/tfvars ignores |
terraform.tfvars.example | core | one entry per variable, source-annotated |
network.tf | networking | VPC, subnets, security groups, ALB/NLB, NAT |
compute.tf | compute | Elastic Beanstalk, ECS/Fargate, EKS, EC2, Lambda |
data.tf | data | RDS/Aurora, ElastiCache, DynamoDB |
storage.tf | storage | S3, EFS/FSx |
messaging.tf | messaging | SQS, SNS, Kinesis/MSK |
security.tf | security | IAM roles, KMS keys, Secrets Manager references |
README.md | core | cost-tier vs Terraform note (one stack, Balanced-aligned) |
Walk aws-design.json services[]. Assign each service to a target .tf file by
aws_service (canonical AWS service on the mapping):
| AWS service (from the mapping) | Target file |
|---|---|
| VPC, VPC subnet, Security Group, ALB, NLB, NAT Gateway, Route 53 | network.tf |
| Elastic Beanstalk, ECS, Fargate, EKS, EC2, Lambda | compute.tf |
| RDS PostgreSQL, RDS MySQL, Aurora, ElastiCache, DynamoDB | data.tf |
| S3, EFS, FSx | storage.tf |
| SQS, SNS, Kinesis, MSK | messaging.tf |
| IAM Role, KMS, Secrets Manager, ECR | security.tf |
Rules:
aws_service: "Deferred — specialist engagement" is NOT generated.
It is carried to generation-warnings.json by the assembler and named in the guide.
Optionally add terraform/README-DEFERRED.md with a one-line checklist.generated[] array ({ azure_id, azure_type, aws_service, target_file }, per
generate-assemble.md); the assembler enforces this.terraform {): state that (1) this directory
implements the single architecture in aws-design.json; (2) the report’s
Premium / Balanced / Optimized figures are three pricing scenarios on the same
map from estimation-infra.json, not three stacks; (3) this Terraform is aligned
with the Balanced scenario; (4) Premium/Optimized require editing the IaC.terraform block: required_version >= 1.5.0, hashicorp/aws ~> 5.80, and an
active (not commented-out) S3 backend block (bucket/key/region/dynamodb_table
with # TODO substitution comments; the state bucket + lock table are emitted in
security.tf, and README documents the two-step init -backend=false bootstrap).provider "aws": region = var.aws_region, default_tags with Project,
Environment, ManagedBy, MigrationId.aws_caller_identity, aws_region, aws_availability_zones.aws_region (from preferences.json target region),
project_name, environment, migration_id.aws_config
(instance classes, sizes, engine versions, capacities). Infer types; use aws_config
values as defaults; deduplicate shared vars. Annotate each with its Azure source as a
comment, e.g. # Azure source: Standard_D2s_v3 (Microsoft.DBforPostgreSQL/flexibleServers).terraform.tfvars.example and whose value cannot be inferred MUST carry a validation
block rejecting the placeholder token (TODO, ACCOUNT_ID, <, example.com), so the
failure happens loudly at terraform plan with a message naming the tfvars key. This is
the azure-specific fill-once set: any DB admin credential is a Secrets Manager reference,
never a variable default.terraform.tfvars.example (one line per variable, source-annotated, descriptive
placeholders — never empty) and .gitignore (terraform.tfvars, *.tfvars,
!terraform.tfvars.example, .terraform/, *.tfstate*).Before generating any .tf, invoke the tf-best-practices skill for its authoring
posture — it is the single source of truth for “what good AWS Terraform looks like.”
Treat it as a black box: tell it you are about to author terraform/ (the
authoring/pre-generation context), pass the caller context below, and emit Terraform
that satisfies whatever posture it returns. Do NOT reach into its files.
Caller context to pass:
compliance — preferences.json → design_constraints.compliance (array; may be
empty/absent). [], absent, and ["none"] ⇒ no compliance-conditional hardening.
["unknown"] ⇒ same catalog as none, plus the report caveat. Named frameworks ⇒
hardening from tf-best-practices.aws_config values — instance classes, CPU/memory, storage, engine versions per
service. The posture constrains shape, not numbers.For each domain with services in the manifest, populate resource attributes from
aws_config and apply these azure-specific rules the skill cannot own:
confidence: inferred → # Tailored to your setup — verify (JSON confidence: inferred); deterministic → optional # Standard pairing.aws-design.json cpu_architecture (azure default is
x86_64, not Graviton — Windows/.NET fleets). cpu_architecture governs COMPUTE
ONLY (ECS/Lambda/EC2/EKS/Elastic Beanstalk). Emit x86 instance/compute classes and, on
compute, an inline note citing the x86 rationale. Only emit ARM64 (arm64 Lambda,
ARM64 ECS runtime_platform, Graviton instance types) when the design explicitly set
Graviton as an optimization.
cpu_architecture; the x86 opt-out
does NOT propagate to database/cache. Emit the exact Graviton class the design/estimate
chose (e.g. db.m6g.large, cache.t4g.*) so generate == estimate == design — never
rewrite a managed RDS/Aurora/ElastiCache class to x86 because the fleet opted out. This is
the shared graviton limitation (Graviton on managed DB/cache has no compatibility risk, so
the opt-out is a consistency choice that stops at compute), matching gcp.Microsoft.Web/serverfarms maps to ONE
compute target (one aws_elastic_beanstalk_environment or one Fargate service), sized
from the PLAN’s SKU + instance count — NOT one per web app. Each Microsoft.Web/sites
folded into the plan contributes its runtime + app settings to that single target’s
configuration; it emits no compute resource of its own. Never multiply compute by
the app count. (This is why the fan-in fold happened in Design; honor it here.)aws_secretsmanager_secret +
aws_secretsmanager_secret_version whose value is a placeholder/var reference or a
# fill in Secrets Manager note — never the source secret value. A Key Vault key
becomes a aws_kms_key. App settings that referenced a Key Vault secret reference the
Secrets Manager ARN.aws_s3_bucket emitted per the
skill’s posture. Every emitted aws_s3_bucket gets a matching
aws_s3_bucket_versioning set to Enabled, PLUS server-side encryption
(aws_s3_bucket_server_side_encryption_configuration) and an
aws_s3_bucket_public_access_block (all four flags true) — and CloudFront/OAC for a
public bucket, compliance-conditional access logging. Never emit a bare aws_s3_bucket
without its versioning/SSE/public-access-block companions. Populate bucket names/lifecycle
from design.preferences.json data.availability. multi-az-ha /
multi-region → Aurora (or Multi-AZ RDS) per the design; single-az → single-AZ RDS.
Do not upgrade beyond what the design chose.aws_elastic_beanstalk_application
and one aws_elastic_beanstalk_environment, resolving solution_stack_name with a
data "aws_elastic_beanstalk_solution_stack" source (do not paste a human-readable
platform label). Emit the EB instance profile it references (aws_iam_role +
aws_iam_instance_profile + AWSElasticBeanstalkWebTier). Emit setting blocks for
IamInstanceProfile, SecurityGroups, InstanceType, EnvironmentType, VPCId/Subnets, and
(LoadBalanced) LoadBalancerType = application.Output identifiers for key resources (VPC ID, DB endpoint, EB env URL, EKS cluster name)
plus a migration_summary object with at least: aws_region, environment,
migration_id, service_count (resources actually emitted in this terraform/),
accounted_count (= generation-warnings.json.accounted: generated + deferred +
skipped/folded — the SAME figure the report body states, so the Terraform summary, the
report body, and generation-warnings.json all agree), aligned_with_estimate_tier =
"balanced", cost_scenarios_modeled_in_terraform = "design_baseline_only".
Description on every output.
.tf; secrets are Secrets Manager refs.{{VARIABLE}} tokens; user-supplied values are var.* with validation.type + description; every output has description.var.aws_region, never hardcoded.aws_s3_bucket has a matching aws_s3_bucket_versioning (Enabled), SSE, and
public-access-block resource — no bare bucket.terraform/README.md exists with the cost-tier vs Terraform note.main.tf begins with the Balanced-alignment header block.This fragment (and the whole Generate phase WORK) runs in the file-only rw worker,
which cannot invoke skills or run terraform/python. So this worker emits terraform/
only: it does NOT invoke the tf-best-practices policy gate, does NOT pin any verdict
path, and does NOT write validation-report.json. The orchestrator (generate.md, main
window) owns Terraform validation, the policy gate, and the single canonical
$MIGRATION_DIR/validation-report.json write after the worker returns — passing only the
terraform DIR to the skill and merging the policy verdict into the report as
policy_status. See generate.md § “Step: Run the phase” step 4.
Report generated files to the parent orchestrator. Do NOT update .phase-status.json
— generate.md handles phase completion. The assembler proves nothing was dropped.
Emitters implemented, following gcp-to-aws’s generate-artifacts-infra.md structure
adapted to azure artifacts: generation manifest, main/variables/outputs core files with
placeholder-guard validation, per-domain files via aws_config, App Service Plan fan-in,
Secrets Manager references, x86 default, and the tf-best-practices authoring hand-off
(Step 3.0). Post-write validation, the policy gate, and the validation-report.json write
now live in the orchestrator (generate.md, main window), not this worker fragment — the
rw worker cannot invoke skills or run terraform/python. The tf-best-practices
invocation is by contract; verify it is installed alongside this skill.