# Production Terraform adoption — Phase 3 review artifacts

This directory is the machine-reviewed handoff for Issue #2515. Stage 00 is
complete; Stage 01 permits only one exact dependency-ordered import batch at a
time through reviewed import blocks and the exact saved targeted plan.

Inventory provenance:

- Terraform inventory baseline: `origin/main` at `aabc5604c`, plus the
  issue-2520 uploads-policy implementation from `cddb110ce`.
- AWS metadata: account `239023060546`, region `ap-southeast-1`, profile
  `ljq-iam`, observed 2026-08-03.
- SSM inventory used `DescribeParameters`. No `SecureString` value was read.
  The one value inspected was `/helix/prod/APP_ENV`, a non-secret `String`,
  because changing `production` to the configured `prod` would alter runtime
  storage isolation.
- Stage 00 applied only the reviewed EC2 inline-policy and ALB readiness-path
  cutovers. No Terraform import, state command, plan or apply has run yet.

## Machine-reviewable files

- `import-manifest.csv` maps all 65 expanded prod resource addresses to the
  observed import identity, batch, and fail-closed status.
- `adoption-contract.json` records state recovery evidence, the coordinated
  replacement-sensitive identities, remaining blockers, and the global gate.
- `stage00-bundle.json` pins every non-secret Stage 00 artifact, including exact
  rollback/candidate/live policy bytes, SSM inventory and canary transcript,
  simulation results, live provenance, and timestamped ALB observations. The
  guard rehashes the whole bundle before plan and apply.
- `production-inputs.tfvars.json` and `production-backend.hcl` are the exact
  non-secret Stage 01 artifacts; the contract pins and tests their SHA256.
- `stage01_guard.py` is the only authorized Stage 01 apply entrypoint. It
  rejects a plan unless its exact address→import-ID map equals the requested
  batch and every managed action is `no-op`.
- `server/tests/test_deploy/test_terraform_prod_adoption_contract.py` checks
  completeness and refuses to treat blocked/missing resources as import-ready.

The manifest statuses are deliberately operational:

- `ready`: an import identity exists and may be used only in its exact Stage 01
  batch after every saved-plan integrity gate passes.
- `state_record_only`: no importer exists; the exact already-live provider
  operation has a separate, still-blocked idempotent state-record strategy.

## Configuration coordination completed

The inspected desired configuration now preserves the live public subnet
CIDRs, security-group descriptions/rules, ECR repository names, EC2 inline
policy identities, Redis identities, ALB 5/2 health thresholds, and listener
policy. `APP_ENV` is also pinned to `production`. The EC2 ECR/SSM/S3 policy and
ALB readiness path now equal desired after their separately canaried Stage 00
cutovers; all inspected adoption values are recorded equal to live.

There are 63 provider-importable addresses, and the manifest exposes all 63
reviewed IDs. The remaining two addresses are provider-only state records and
remain blocked until Stage 02.

The four security groups retain only their live `Name`/`Project` tags; the
target group and listeners retain their live empty tag sets. The non-secret
`CORS_ORIGINS` parameter uses the live JSON-array string consumed by Settings.
Tag governance and CORS shape changes are separate from state adoption.

AWS Terraform no longer declares the DNSPod records or the nine SecureString
parameters. `external-ownership.json` records those ownership boundaries
without secret values. ECR lifecycle policies remain disabled because none
exist live. SSM-only access removes the nonexistent SSH rule; all live SG rules
are explicitly declared so a later apply cannot silently delete them.
This evidence is not a complete replacement guarantee: production inputs are
now pinned and controlled, but no reviewed plan JSON exists. The contract therefore
records `replacement_assurance=not_yet_plan_verified`. The staged gates require
zero create/update/delete/replace actions for each import-only saved plan, allow
exactly one reviewed create for each state-record saved plan, and finally
require a full zero-action plan.

## Exact Stage 01 controls

1. The pinned non-secret runtime tfvars and backend artifacts must match the
   SHA256 values in `adoption-contract.json` immediately before every plan.
2. The ACM validation waiter and target-group attachment have no provider
   importer. `state-record-strategy.json` specifies two isolated operations,
   but neither is authorized yet. The attachment operation calls
   `RegisterTargets`; it is an idempotent production write only when the exact
   healthy tuple is still registered and not draining. Terraform ownership also
   means a future destroy calls `DeregisterTargets`, so the resource has
   `prevent_destroy` and requires explicit break-glass handling.
3. No saved plan has been reviewed. Every stage records the SHA256 of its plan
   file and applies that exact file; running `terraform apply -target=...` and
   allowing Terraform to replan is forbidden.

## Mandatory Stage 01 machine gate

Run the guard only with the project's Python 3.12+ runtime, for example
`uv run --project server python
infra/aws/environments/prod/adoption/stage01_guard.py plan`. Pass output paths,
the reviewed tfvars/backend files, and every prior batch evidence file. The
guard creates its own mode-0700 temporary workspace, seals the pinned Terraform
binary, source, inputs and provider set there, and creates the saved
targeted plan and JSON itself; caller-generated plans are not accepted. It captures the live AWS
account, region and state object VersionId/lineage/serial itself; it does not
accept those values from operator text. It also reruns `terraform show -json`
with the pinned Terraform binary and requires the output hash to equal the
provided plan JSON, so a forged summary cannot be paired with another plan.
Review evidence must be committed and
reviewed through a PR, merged to `origin/main`, and byte-identical to the merged
blob before apply.

The only authorized apply is `uv run --project server python
infra/aws/environments/prod/adoption/stage01_guard.py apply
--execute-reviewed-plan`. Immediately before invoking Terraform it recomputes
all artifact hashes, reparses the plan JSON, recaptures AWS identity/region and
the exact state marker, then calls `terraform apply` with only the exact saved
plan file. It never replans and never accepts an apply-time target. On success
it first persists that Terraform returned success, then atomically records the
new state marker before requiring health, readiness and root production probes
to return 200. A probe failure therefore cannot erase evidence that state has
already changed. Direct `terraform import`, `terraform state` mutation and
ordinary/replanned apply remain forbidden.
If Terraform returned success, or the process outcome became ambiguous after
`apply_started`, use the guard's `finalize` subcommand. It advances only when
the exact VersionId-bracketed state delta equals the whole reviewed batch with
no deletion or extra address; known failed/interrupted outcomes require an
advanced marker and the same proof. It then rechecks fixed no-redirect HTTPS
probes and contains no Terraform execution path. An `OSError` before process
launch with an unchanged marker restores the committed reviewed evidence for a
safe retry; every other incomplete delta remains blocked.

Canonical first-batch invocation (later batches append one
`--previous-evidence` for every prior applied evidence file):

```bash
plan_dir="$(mktemp -d /private/tmp/helix-01-network.XXXXXX)"
chmod 700 "$plan_dir"
AWS_PROFILE=ljq-iam AWS_REGION=ap-southeast-1 uv run --project server python \
  infra/aws/environments/prod/adoption/stage01_guard.py plan \
  --batch 01-network \
  --plan "$plan_dir/reviewed.tfplan" \
  --plan-json "$plan_dir/reviewed.tfplan.json" \
  --tfvars infra/aws/environments/prod/adoption/production-inputs.tfvars.json \
  --backend-config infra/aws/environments/prod/adoption/production-backend.hcl \
  --evidence infra/aws/environments/prod/adoption/batches/01-network.json \
  --terraform /private/tmp/helix-terraform.JWq9CQ/terraform \
  --terraform-root "$PWD/infra/aws/environments/prod" \
  --repo-root "$PWD"
```

The Terraform path is accepted only when its SHA256 equals the contract; the
guard rejects another build even if it reports the same version. Apply uses the
same arguments plus `apply --execute-reviewed-plan` after the evidence PR is
merged. `finalize` accepts only `--repo-root`, `--evidence`,
`--backend-config`, and `--contract`; it never accepts a Terraform binary or
plan path.

## Staged execution gates

The contract deliberately separates evidence needed before state exists from
proof that is possible only after state is recorded:

1. `00_pre_import_controls` — **completed**: the IAM candidate passed simulation
   and a digest-pinned runtime canary after rollback was exercised once for a
   canary-assumption defect; the corrected run passed. The ALB changed only to
   `/api/v1/ready` and stayed healthy through five 30-second windows. Non-secret
   exact rollback/candidate/live bytes and timestamped observations are pinned
   by `stage00-bundle.json` and mechanically rehashed at both Stage 01 gates.
2. `01_import_batches`: render one dependency-ordered import batch and create a
   saved plan targeted to every address in exactly that batch. All dependencies
   must already be in prior-batch state; targeting prevents later unimported
   resources from appearing as creates. Inspect plan JSON for exact targets and
   imports, no later-batch address, and zero create/update/delete/replace
   actions, except the isolated `06-acm-validation` waiter state record, which
   requires the certificate already imported and live ACM/DNS validation
   preconditions and may contain exactly one create with no cloud mutation.
   The JSON review must be generated from the exact saved plan. Record
   its SHA256 and the current state VersionId/lineage/serial marker (explicit
   object absence for batch one). Immediately before apply, recompute the hash,
   verify AWS account/region and the unchanged state marker, then apply only
   that saved plan. Record the new marker after every batch.
3. `02_state_records`: after all 63 importable addresses refresh cleanly, make
   one saved targeted plan for the remaining target-group attachment. It may
   create only that exact address.
   Apply the same hash/account/region/state-marker checks, then recheck live
   preconditions immediately before applying the matching saved plan. The
   target attachment step is a production write and requires health verification
   immediately afterward. Terraform's stale-plan rejection is an additional
   guard, not a substitute for these checks.
4. `03_final_zero_action`: run a full non-targeted plan and prove all four
   action counts are zero. Only this result completes adoption.

## Reviewed batch order

The CSV batches express dependency order, not permission to execute:

1. `01-network` — VPC, subnets, NAT, routes, associations.
2. `02-security` — security groups first, then existing rules.
3. `03-ecr` — repositories only; lifecycle policies are disabled.
4. `04-iam` — OIDC provider, roles, policies that do not depend on EC2, instance
   role, profile, and attachment.
5. `05-compute` — EC2 only after network, SG, and instance profile.
6. `05-post-compute-iam` — DAX and production-deploy policy documents only
   after the EC2 instance ARN is known from imported state.
7. `05-acm-certificate` — import the already-issued ACM certificate.
8. `06-acm-validation` — create only its local waiter state record after live
   ACM status and DNS CNAME checks pass; no cloud mutation.
9. `06-edge` — ALB, target group and listeners after the waiter; DNS remains
   externally owned by DNSPod.
10. `07-cache` — subnet group before the Redis replication group.
11. `08-parameters` — four non-secret SSM parameters only; SecureString paths
   remain externally owned and never enter Terraform state.

No batch may be rendered during stage `00_pre_import_controls`. Advancing stages
requires the prior stage's evidence to be committed and reviewed; no stage
authorizes an ordinary or replanned apply.
