# Frontend Terraform adoption runbook This tree adopts the existing Sea Haven SHOC frontend hosting resources without recreating them. It implements the local configuration and plan-safety tooling only. Creating these files, formatting them, initializing with `-backend=false`, and validating them does not authorize an AWS, HCP Terraform, GitHub, CloudFormation, DNS, or deployment mutation. The rollout order is `tf-poc`, dev, then staging. Production and tf-poc teardown are separate follow-up changes. ## Fixed targets - AWS account: `396287094661` - AWS region: `us-east-1` - HCP organization: `seahaven` - HCP project: `seahaven-external-dev` - Workspaces: - `shoc-frontend-new-tf-poc` - `shoc-frontend-new-dev` - `shoc-frontend-new-staging` - HCP auto-apply: off for all three workspaces - tf-poc site: `frontend-tf-poc.seahaven.com` - tf-poc API build value: `https://api.tf-poc.seahaven.com/api` - tf-poc bucket: `seahaven-shoc-frontend-tf-poc` - tf-poc deploy role: `githubdeploy-shoc-frontend-new-tf-poc` - tf-poc GitHub environment: `tf-poc` The `cloud` blocks identify the organization, project, and workspace. Auto-apply is an HCP workspace setting and must be verified operationally before connecting VCS or starting a run. ## Ownership boundary `live/modules/environment-owned` owns exactly these 13 addresses: 1. `module.environment_owned.aws_s3_bucket.site` 2. `module.environment_owned.aws_s3_bucket_public_access_block.site` 3. `module.environment_owned.aws_s3_bucket_ownership_controls.site` 4. `module.environment_owned.aws_s3_bucket_server_side_encryption_configuration.site` 5. `module.environment_owned.aws_s3_bucket_versioning.site` 6. `module.environment_owned.aws_s3_bucket_policy.site` 7. `module.environment_owned.aws_cloudfront_distribution.site` 8. `module.environment_owned.aws_cloudfront_origin_access_control.site` 9. `module.environment_owned.aws_cloudfront_function.spa_rewrite` 10. `module.environment_owned.aws_route53_record.site_a` 11. `module.environment_owned.aws_route53_record.site_aaaa` 12. `module.environment_owned.aws_iam_role.github_deploy` 13. `module.environment_owned.aws_iam_role_policy.github_deploy` Every managed resource has `prevent_destroy = true`. `live/modules/environment-inventory` is data-only. It resolves and checks the caller account, provider region, public hosted zone, ACM certificate, account GitHub OIDC provider, and AWS managed `Managed-CachingOptimized` CloudFront cache policy. The following remain outside state: - public hosted zones and ACM certificates - the account-global GitHub OIDC provider - the AWS managed CloudFront cache policy - `CDKToolkit` resources and CDK metadata - S3 auto-delete custom resources, provider Lambda, provider role, and log group - hosted-zone and ACM validation internals - CloudFront service-generated resources ## Exact live inventory ### Dev - Bucket and all bucket subresources: `seahaven-shoc-frontend-dev` - Distribution: `E2CWLM1AFB964P` - OAC: `E30VSIK87N8H64` - OAC name: `shocfrontenddevDistributionOrigin1S3OriginAccessControlDFC82620` - OAC description: the API empty value, modeled as `""` - Distribution origin ID: `shocfrontenddevDistributionOrigin10CCD0EE1` - Function: `us-east-1shocfrontenddevSpaRewrite58674DB8` - A import ID: `Z07671212N75U4YLPWZR8_dev.seahaven.com_A` - AAAA import ID: `Z07671212N75U4YLPWZR8_dev.seahaven.com_AAAA` - Deploy role: `githubdeploy-shoc-frontend-new-dev` - Inline policy import ID: `githubdeploy-shoc-frontend-new-dev:GithubDeployRoleDefaultPolicyE8F540D1` - Hosted zone: `Z07671212N75U4YLPWZR8` - Stack: `shoc-frontend-dev` ### Staging - Bucket and all bucket subresources: `seahaven-shoc-frontend-staging` - Distribution: `E2JDVEZ6EGD49J` - OAC: `E1PF5R6QQNBZAI` - OAC name: `shocfrontendstagingDistributOrigin1S3OriginAccessControl82B1C17D` - OAC description: the API empty value, modeled as `""` - Distribution origin ID: `shocfrontendstagingDistributionOrigin16E4628FC` - Function: `us-east-1shocfrontendstagingSpaRewriteE9C0CBDA` - A import ID: `Z02602739VQWBWCAGXP4_staging.seahaven.com_A` - AAAA import ID: `Z02602739VQWBWCAGXP4_staging.seahaven.com_AAAA` - Deploy role: `githubdeploy-shoc-frontend-new-staging` - Inline policy import ID: `githubdeploy-shoc-frontend-new-staging:GithubDeployRoleDefaultPolicyE8F540D1` - Hosted zone: `Z02602739VQWBWCAGXP4` - Stack: `shoc-frontend-staging` Both live roots inventory the shared certificate: `arn:aws:acm:us-east-1:396287094661:certificate/2b78e74f-7b65-4b82-a413-7a498b102f00` The live roots preserve the observed pre-adoption configuration: - `adoption_complete = false` - `Environment`, `ManagedBy=cdk`, and `Project=shoc-frontend` tags - the S3-only `aws-cdk:auto-delete-objects=true` tag - the deploy-role-only `HcpTerraformWorkspace=shoc-frontend-new-` manager tag - current OAC names, empty descriptions, origin IDs, comments, protocols, cache policy, certificate, trust subjects, role descriptions, and legacy deploy policy - the exact legacy bucket-policy grant for the S3 auto-delete helper - the mandatory deterministic permissions boundary `arn:aws:iam::396287094661:policy/shoc-frontend-new--deploy-boundary` The approved legacy-owner prerequisite narrows the dev role's exact subject from `StringLike` to `StringEquals` before import. All roots therefore use `StringEquals` in both modes. The HCP workspace manager tag remains on the role in both modes and is never added to S3 or CloudFront resources. If a prerequisite changes any live metadata before import, update the matching root to the newly observed exact value and prove a zero-change import plan. Do not approve that drift through the controlled-update checker. ## Prerequisites Before any remote plan: 1. Confirm the deployment workflow for the target environment is paused while PR validation remains active. 2. Confirm no CloudFormation/CDK update or content deployment can race the adoption. 3. Confirm the HCP workspace is in the `seahaven-external-dev` project with auto-apply off. 4. Confirm the org-baseline plan/apply roles and deploy-role permissions boundary exist with exact workspace trust. 5. Attach the root's deterministic boundary through the approved legacy-owner procedure. It is mandatory before the import plan. 6. Verify account `396287094661`, region `us-east-1`, all import IDs, current tags, the dev `StringEquals` trust prerequisite, role description, boundary, policies, distribution configuration, OAC configuration, function code, DNS targets, and bucket settings using read-only queries. 7. Confirm the creator stack has retention semantics for all 13 transferred resources and the S3 auto-delete custom resource. A synthesized template and reviewed change set are required before mutation. 8. Capture a complete object-version inventory and smoke-test baseline. Do not remove or replace the S3 auto-delete custom resource casually. Deleting it while its handler is active can empty the versioned bucket. Retain it during ownership transfer and prove the tf-poc path before touching dev. ## HCP variables Configure dynamic AWS credentials in each workspace. Use the exact org-baseline role ARNs for that workspace: - environment variable `TFC_AWS_PROVIDER_AUTH=true` - environment variable `TFC_AWS_PLAN_ROLE_ARN` - environment variable `TFC_AWS_APPLY_ROLE_ARN` - Terraform variable `adoption_complete=false` Do not store AWS access keys. Mark sensitive values sensitive even when they are not credentials. VCS working directories are: - `terraform/live/tf-poc` - `terraform/live/dev` - `terraform/live/staging` tf-poc also requires every variable in `terraform.tfvars.example`. Populate them only from creator outputs and read-only verification. The check in the tf-poc root blocks planning while a value is empty or starts with `REPLACE_WITH_`. ## Local validation From the repository root: ```powershell terraform fmt -check -recursive terraform python scripts/test-terraform-import-plan-check.py ``` For every root: ```powershell terraform -chdir=terraform/live/tf-poc init -backend=false terraform -chdir=terraform/live/tf-poc validate terraform -chdir=terraform/live/dev init -backend=false terraform -chdir=terraform/live/dev validate terraform -chdir=terraform/live/staging init -backend=false terraform -chdir=terraform/live/staging validate ``` Initialization without the backend may download providers and write lockfiles, but it must not contact HCP state or plan against AWS. ## tf-poc flow 1. Deploy only the temporary shared stack with `tfPocPhase=zone` and record its name servers. 2. Apply the separately approved parent-zone NS delegation and verify it publicly. 3. Use `tfPocPhase=environment` to add the certificate and environment stack. Do not attempt certificate creation before delegation. 4. Record creator outputs for the distribution, OAC ID/name, function, zone, certificate, origin ID, role, inline policy, bucket auto-delete helper role, and DNS import IDs. 5. With the frontend tf-poc HCP role gate still false, set the five org-baseline tf-poc identifiers from those outputs and deploy the reviewed boundary update. Confirm the creator role's existing boundary now permits only its bucket operations and exact distribution invalidation. 6. Deploy the real SPA through GitHub environment `tf-poc`, built with `VITE_API_URL=https://api.tf-poc.seahaven.com/api`. 7. Pass HTTPS page load, `/login`, extensionless SPA fallback, asset-reference integrity, expected/forbidden API URL scan, cache headers, invalidation completion, API CORS/preflight connectivity, and index rollback. 8. Populate HCP variables. Re-run read-only inventory and compare all declared metadata. 9. Produce the import plan, export JSON, and pass the zero-change import gate. 10. Review and apply only the imports. Require an immediate second no-op plan. 11. Prepare and inspect retention for all transferred resources and the auto-delete custom resource. Do not detach yet. 12. Set only tf-poc `adoption_complete=true`. Run the controlled-update gate with the exact addresses below, apply after review, and require a no-op plan. This removes the bucket policy grant while the CDK auto-delete helper role still exists. 13. Detach the creator stack with the reviewed retention template. Verify identifiers, every object version, DNS, HTTPS/API smoke checks, deploy-role assumption, and a final no-op plan. Stop on a missing output, placeholder, nonzero import action, unexpected address, replacement, inventory mismatch, retention mismatch, or smoke failure. ## Import plan safety Create a saved plan using the approved remote workflow, then export its JSON: ```powershell terraform show -json path\to\saved.plan > path\to\plan.json python scripts/check-terraform-import-plan.py path\to\plan.json --environment tf-poc ``` Use `dev` or `staging` for the corresponding root. Import mode requires: - exactly the canonical 13 addresses and AWS types - valid import metadata for every resource - exact known import IDs for dev and staging - populated, non-placeholder creator IDs for tf-poc - zero create, update, delete, or replace actions After import apply, export the immediate refresh plan and use the distinct post-import mode. It requires all 13 resources to be no-op and rejects any remaining import metadata: ```powershell python scripts/check-terraform-import-plan.py path\to\post-import-plan.json ` --environment tf-poc ` --post-import-no-op ``` The exact controlled-adoption addresses are: - `module.environment_owned.aws_s3_bucket.site` - `module.environment_owned.aws_s3_bucket_policy.site` - `module.environment_owned.aws_cloudfront_distribution.site` - `module.environment_owned.aws_cloudfront_function.spa_rewrite` - `module.environment_owned.aws_iam_role.github_deploy` - `module.environment_owned.aws_iam_role_policy.github_deploy` The bucket-policy update removes only the retained auto-delete helper grant. The IAM role update changes ownership tags while preserving the exact `StringEquals` subject, boundary, and HCP manager tag. Run controlled mode by repeating the exact option: ```powershell python scripts/check-terraform-import-plan.py path\to\plan.json ` --environment tf-poc ` --allow-update-address module.environment_owned.aws_s3_bucket.site ` --allow-update-address module.environment_owned.aws_s3_bucket_policy.site ` --allow-update-address module.environment_owned.aws_cloudfront_distribution.site ` --allow-update-address module.environment_owned.aws_cloudfront_function.spa_rewrite ` --allow-update-address module.environment_owned.aws_iam_role.github_deploy ` --allow-update-address module.environment_owned.aws_iam_role_policy.github_deploy ``` Controlled mode permits only in-place updates to the addresses explicitly listed on that invocation. It rejects create, delete, replace, import metadata, unapproved addresses, and unused allowlist entries. OAC and Route 53 must remain unchanged. If an ownership-tagged resource does not actually update because its final tags are already present, omit that address from both the plan expectation and the command. Never leave an unused allowlist entry. ## Dev and staging flow Run one live environment at a time. For dev: 1. Keep releases paused. 2. Complete and verify boundary, retention, and exact-metadata prerequisites. 3. Run and review the zero-change import plan. 4. Apply imports and require a second no-op plan. 5. Prepare and verify the retention template only after tf-poc evidence is accepted. Do not detach yet. 6. Set `adoption_complete=true`, allow only the exact updating addresses from the controlled list, apply after review, and require another no-op plan. 7. Detach CloudFormation with the reviewed retention template. 8. Verify IDs, object versions, DNS, TLS, API connectivity, content deployment, invalidation, rollback, deploy identity, and a final no-op plan. 9. Re-enable dev release only after explicit approval. Observe dev for the agreed window. Then repeat the full sequence for staging. Staging termination protection requires a separately reviewed disable immediately before retained stack deletion. Do not carry approval from dev into staging. ## Evidence Retain for each phase: - HCP run URL and workspace settings showing auto-apply off - saved plan JSON and checker output - state list containing exactly the 13 managed addresses - read-only inventory before and after each mutation - synthesized CloudFormation template, reviewed change set, and stack events - object-version inventory - exact DNS, certificate, distribution, OAC, function, role, and policy IDs - deployment, invalidation, smoke, and rollback output - post-action no-op plan - phase close-out with completed work, validation, risks, deviations, and remaining work ## Rollback - Before import apply: discard the run and correct configuration. - After import but before the controlled ownership update: remove only the imported Terraform state addresses under a separately reviewed state operation. CloudFormation remains authoritative. - After the controlled ownership update but before detachment: do not simply remove Terraform state or redeploy CloudFormation. Either complete the reviewed retained detachment or explicitly restore the exact pre-adoption policy and tags under a separate rollback approval. - After detachment: Terraform remains authoritative. Restore content from the versioned bucket and release manifests. Do not recreate the legacy stack over retained resources. - Re-establishing CloudFormation ownership requires a reviewed CloudFormation `IMPORT` change set. An ordinary create/update is not a rollback. Any replacement, destroy, cross-environment ID, missing import, broad policy change, or failed smoke check is a hard stop.