# Frontend infrastructure and migration rehearsal This CDK app describes the existing Sea Haven SHOC SPA hosting and an isolated, production-shaped Terraform adoption rehearsal. It performs no content upload. Content-only deployment is handled by `scripts/deploy-web.sh`. ## Existing environments Normal synthesis remains unchanged when the adoption flag is off: - private, versioned S3 bucket with CDK auto-delete cleanup - CloudFront distribution and origin access control - viewer-request function that rewrites extensionless SPA routes - optional Route 53 A and AAAA aliases - GitHub Actions OIDC deploy role Dev remains the default context in `cdk.json`. Staging uses explicit context arguments. During migration, both content workflows are manual-only and use fixed environment configuration rather than discovering deployment targets from CloudFormation. ## Terraform POC The POC is isolated in account `396287094661`, region `us-east-1`, and is created only with `-c tfPoc=true` plus an explicit `tfPocPhase`. It uses three ownership scopes: 1. `shoc-frontend-tf-poc-shared`: a dedicated public hosted zone for `frontend-tf-poc.seahaven.com`. 2. `shoc-frontend-tf-poc-certificate`: the DNS-validated ACM certificate. 3. `shoc-frontend-tf-poc`: the private versioned bucket, CloudFront OAC, distribution, SPA function, A/AAAA aliases, and GitHub OIDC content deploy role. Fixed application values: - bucket: `seahaven-shoc-frontend-tf-poc` - role: `githubdeploy-shoc-frontend-new-tf-poc` - GitHub environment: `tf-poc` - site: `https://frontend-tf-poc.seahaven.com` - API: `https://api.tf-poc.seahaven.com/api` The shared stack is intentionally staged. The zone must exist and be delegated before ACM can validate a certificate inside it: ```bash cd infra/cdk npm ci npm test npm run synth:tf-poc-zone npm run synth:tf-poc-environment ``` After approval, deploy only `shoc-frontend-tf-poc-shared` with `tfPocPhase=zone`. Its outputs provide the child name servers. Create the parent NS record as a separate approved change and verify public delegation. Only then use `tfPocPhase=environment` to deploy the separate certificate and site stacks. The zone stack never contains the certificate, so re-running the zone phase cannot remove a certificate created by the environment phase. Omitting `tfPocPhase` fails closed. The stacks output the hosted zone ID, certificate ARN, delegation evidence, workspace tag, boundary ARN, and import IDs for the bucket, bucket policy, distribution, OAC, SPA function, A/AAAA records, deploy role, and inline role policy. They also emit the generated OAC name/description, deterministic origin ID, and inline policy name required by the tf-poc Terraform configuration. ## Adoption retention `-c retainForTerraformAdoption=true` is deliberately opt-in. Keep it enabled from the reviewed retention deployment through CloudFormation ownership detachment. The emitted template applies both `DeletionPolicy: Retain` and `UpdateReplacePolicy: Retain` to: - site bucket and bucket policy - distribution, OAC, and SPA rewrite function - A and AAAA records - GitHub deploy role and its inline policy - `SiteBucket/AutoDeleteObjectsCustomResource` The bucket remains configured with `autoDeleteObjects: true`. The emitted bucket and its matching custom resource are both retained, so deleting the stack cannot invoke that custom resource to empty the versioned bucket. Generated provider Lambda resources, provider IAM resources, provider logs, and CDK metadata are intentionally excluded. Template tests enforce this exact boundary. In adoption mode, the deploy role also receives: - tag `HcpTerraformWorkspace=shoc-frontend-new-{env}` - permissions boundary `arn:aws:iam:::policy/shoc-frontend-new-{env}-deploy-boundary` - exact `StringEquals` OIDC subject matching; for dev this narrows the current no-wildcard `StringLike` subject before Terraform import These changes are absent when the flag is off. ## Content deployment safeguards `scripts/deploy-web.sh` requires explicit target and expectation variables: - `SITE_BUCKET` and matching `EXPECTED_SITE_BUCKET` - `CLOUDFRONT_DISTRIBUTION_ID` - `SITE_URL` - `VITE_API_URL` and matching `EXPECTED_API_URL` - `DEPLOY_RELEASE_ID` or `GITHUB_SHA` - optional comma-separated `FORBIDDEN_API_URLS` - optional `API_SMOKE_URL` and `API_CORS_ORIGIN` The script verifies bucket versioning, builds the app, publishes immutable assets and a no-cache index, records a release manifest, invalidates and waits, then checks `/`, `/login`, an extensionless route, asset references, API URLs, and cache headers. Optional API preflight checks verify CORS. If verification fails after publishing the index, the previous index version is restored and invalidated. Pruning starts only after successful remote verification. Current versions needed by the latest two release manifests are kept; unreferenced object versions are deleted. ## Manual workflows - `.github/workflows/deploy.yml`: dev content deployment - `.github/workflows/deploy-staging.yml`: staging content deployment - `.github/workflows/deploy-tf-poc.yml`: isolated POC content deployment All are `workflow_dispatch` only. Staging and tf-poc retain their GitHub environment protection and exact environment-scoped OIDC trust. Each dev and staging distribution ID is pinned in its workflow. The tf-poc environment must define its generated `CLOUDFRONT_DISTRIBUTION_ID` as a protected variable. Each workflow pins its environment's Swagger URL for API CORS/preflight checks. Infrastructure creation, parent-zone delegation, Terraform imports, and ownership detachment remain separate administrator actions. None of these workflows performs them.