docs: document the dev Terraform adoption runbook and gates

Two-phase runbook, ownership boundary, workspace invariants, rollback
per phase, and operational rules in terraform/README.md; CDK adoption
mode and the retired cd-cdk path in infra/cdk/README.md; gate matrix and
deployment section updates.
This commit is contained in:
Adam Moussa 2026-09-10 19:15:14 -04:00
parent 08da408a13
commit b71c0ad87b
No known key found for this signature in database
2 changed files with 100 additions and 51 deletions

View file

@ -7,7 +7,10 @@ npm run verify
```
`verify` chains: `format:check` → `lint` → `build` (`tsc -b && vite build`) →
`test` (`vitest run`) → `governance`. A task is not done until this is green.
`test` (`vitest run`) → `governance`. Governance also runs the repository
gates: the Terraform import-plan checker tests, the Terraform isolation gate
tests, Terraform formatting and validation, and the CDK build, template tests,
and synthesis. A task is not done until this is green.
## Gate matrix
@ -23,6 +26,11 @@ npm run verify
| Hooks correctness | `eslint-plugin-react-hooks` recommended (incl. `exhaustive-deps`) under zero-warnings | lint | Governed TS/TSX |
| Godfile ratchet (file length) | `scripts/governance-check.mjs` + `scripts/governance-baseline.json` | `governance` | `src/**`, `config/**` (non-test) |
| Changed-file maintainability | `scripts/governance-check.mjs` → ESLint (`complexity`, `max-lines-per-function`, `max-params`, `max-depth`) | `governance` | Changed TS/TSX vs base ref |
| Terraform import-plan contract | `npm run test:terraform-import-plan` → `scripts/test-terraform-import-plan-check.py` | `governance` + CI | Synthetic plan JSON + canonical maps |
| Terraform isolation gate contract | `npm run test:terraform-isolation` → `scripts/check-terraform-isolation.test.mjs` | `governance` + CI | Changed-file classifier |
| Terraform formatting/validation | `npm run test:terraform` → `scripts/terraform-validate.mjs` | `governance` + CI | `terraform/live/dev` |
| CDK build, tests, synthesis | `npm run test:infra` | `governance` + CI | `infra/cdk/**`, both synth modes |
| Terraform/app change isolation | `.github/workflows/terraform-isolation.yaml` → `scripts/check-terraform-isolation.mjs` | CI (PR) | Changed files of the PR |
## No-false-pass guarantees
@ -36,6 +44,14 @@ npm run verify
- **Changed-file maintainability fails closed without a valid base** — in CI the
base ref is derived from `GITHUB_BASE_REF` (PR) or `github.event.before`
(push). An absent or unresolvable base is a failure, not a pass.
- **Terraform gates never touch live state** — `terraform init -backend=false
-lockfile=readonly` and `validate` run offline; the plan checker is tested
against synthetic plan JSON. Real import and controlled-update plans from HCP
are migration evidence reviewed by a human before an approved apply
(`terraform/README.md`).
- **The isolation gate re-evaluates on label changes** — the
`terraform-isolation-override` label is the only way to merge a mixed
Terraform/application PR, and the gate logs the override on the run.
## Where the gates run
@ -44,11 +60,17 @@ npm run verify
- **CI ([`.github/workflows/ci.yaml`](.github/workflows/ci.yaml)):** the org
reusable workflow (`ci-typescript-frontend.yaml`, Node 24) runs
format/lint/build/tests, **and** a repo-owned `governance` job runs
`npm run verify` so the maintainability ratchets are guaranteed from this
repository regardless of the reusable workflow.
`npm run verify` (with Terraform 1.16.0 installed) so the maintainability
ratchets and repository gates are guaranteed from this repository regardless
of the reusable workflow.
- **CI ([`.github/workflows/terraform-isolation.yaml`](.github/workflows/terraform-isolation.yaml)):**
on every PR (including label events), fails when `terraform/**` and
application code change together.
## Toolchain pin
Node ≥ 22.22.1 (CI uses Node 24); npm 11.16.0 via `packageManager` (use
`corepack npm …` if your default `npm` is older). The lockfile is
`package-lock.json` v3; install with `npm ci`.
`package-lock.json` v3; install with `npm ci`. Governance also needs
`terraform` (CI: 1.16.0; `versions.tf` accepts `>= 1.9.0, < 2.0.0`) and
`python3` (3.10+) on `PATH`.

121
README.md
View file

@ -29,10 +29,15 @@ graph LR
CF -->|OAC| S3[S3 seahaven-shoc-frontend-dev]
CF -.->|viewer-request fn| FN[SPA rewrite → /index.html]
U -->|HTTPS api.dev.seahaven.com/api CORS| API[SHOC backend API]
GH[GitHub Actions push to dev] -->|OIDC| ROLE[githubdeploy-shoc-frontend-new-dev]
ROLE -->|cdk deploy + s3 sync + invalidation| S3
GH[GitHub Actions: Deploy dev content] -->|OIDC| ROLE[githubdeploy-shoc-frontend-new-dev]
ROLE -->|s3 sync + invalidation| S3
TF[HCP Terraform shoc-frontend-new-dev] -.->|adopting: bucket, CloudFront, DNS, role| S3
```
Dev hosting is being adopted from CDK into HCP Terraform (SH-300); see
[`terraform/README.md`](terraform/README.md) for the phase runbook and the
current ownership state.
Frontend stack: React 19, TypeScript, Vite, Tailwind CSS 4 + MUI, TanStack
Query, React Router (via `@generouted/react-router`), React Hook Form + Zod,
Ky HTTP client. Source layout: `src/api/`, `src/domain/`, `src/app/` (see the
@ -57,12 +62,13 @@ No Lambdas, queues, or databases — this stack is static hosting only.
### Secrets
No Secrets Manager or SSM parameters. The one secret is a **GitHub Actions
repo secret**:
No Secrets Manager or SSM parameters. AWS access is OIDC only; the deploy role
ARNs are deterministic and pinned in the workflows. The one **GitHub Actions
repo secret** is:
| Secret | Purpose |
| --------------------- | ----------------------------------------------------------------------------------- |
| `AWS_DEPLOY_ROLE_ARN` | ARN of `githubdeploy-shoc-frontend-new-dev`, passed to the org reusable CD workflow |
| Secret | Purpose |
| ------------------- | ------------------------------------------------------------------ |
| `SENTRY_AUTH_TOKEN` | Source-map upload by `scripts/upload-sourcemaps.sh` after a deploy |
### Environment variables (build-time, `VITE_*`)
@ -78,7 +84,8 @@ build otherwise. See [`.env.example`](.env.example),
[`.env.development`](.env.development), and [`.env.production`](.env.production).
CDK context (domain, certificate ARN, hosted zone) lives in
[`infra/cdk/cdk.json`](infra/cdk/cdk.json) so CI runs `cdk deploy` with no flags.
[`infra/cdk/cdk.json`](infra/cdk/cdk.json) so an administrator runs
`cdk deploy` with no flags.
## Local Development
@ -95,17 +102,22 @@ The dev proxy expects the `shoc-backend` API at `http://localhost:5141`;
override with `VITE_API_TARGET` (e.g. `https://api.dev.seahaven.com` to use
the deployed dev API).
| Command | Description |
| ------------------------------------------ | -------------------------------------------------------- |
| `npm run dev` | Start Vite dev server on port 3000 |
| `npm run build` | Type-check (`tsc -b`) and production build to `dist/` |
| `npm run preview` | Preview the production build locally |
| `npm test` / `npm run test:watch` | Vitest unit tests (once / watch) |
| `npm run test:e2e` / `npm run test:e2e:ui` | Playwright e2e tests (headless / UI mode) |
| `npm run lint` / `npm run lint:fix` | ESLint (check / auto-fix) |
| `npm run format` / `npm run format:check` | Prettier (write / check) |
| `npm run governance` | Frontend governance checks (godfile + maintainability) |
| `npm run verify` | **All gates**: format + lint + build + test + governance |
| Command | Description |
| ------------------------------------------ | ------------------------------------------------------------ |
| `npm run dev` | Start Vite dev server on port 3000 |
| `npm run build` | Type-check (`tsc -b`) and production build to `dist/` |
| `npm run preview` | Preview the production build locally |
| `npm test` / `npm run test:watch` | Vitest unit tests (once / watch) |
| `npm run test:e2e` / `npm run test:e2e:ui` | Playwright e2e tests (headless / UI mode) |
| `npm run lint` / `npm run lint:fix` | ESLint (check / auto-fix) |
| `npm run format` / `npm run format:check` | Prettier (write / check) |
| `npm run governance` | Governance checks (godfile, maintainability, Terraform, CDK) |
| `npm run verify` | **All gates**: format + lint + build + test + governance |
`npm run governance` needs `terraform` and `python3` on `PATH` for the
Terraform gates (`npm run test:terraform`, `npm run test:terraform-import-plan`,
`npm run test:terraform-isolation`) and installs `infra/cdk` for
`npm run test:infra`.
Husky + lint-staged run ESLint and Prettier on staged files at commit;
commitlint enforces conventional commit messages. Run `npx tsc --noEmit` (or
@ -123,66 +135,81 @@ commitlint enforces conventional commit messages. Run `npx tsc --noEmit` (or
a green CI run and an approving review from a code owner
(`@Sea-Haven-Industries/internal-dev`); new pushes dismiss stale approvals.
Merged branches are deleted automatically.
- Promotion flow: `feature/* → dev` (auto-deployed and verified on
`dev.seahaven.com`) `→ main` (production promotion — no prod environment
exists yet).
- A PR that changes `terraform/**` may not also change application code
(`.github/workflows/terraform-isolation.yaml`); ship Terraform in its own PR.
- Promotion flow: `feature/* → dev` (deployed to `dev.seahaven.com` through the
**Deploy dev content** workflow while the Terraform adoption is in progress)
`→ main` (production promotion — no prod environment exists yet).
## Deployment
CI/CD uses the org's reusable workflows (no stored AWS keys — OIDC only):
No stored AWS keys — OIDC only. Infrastructure and content deploy separately:
- **CI** ([`.github/workflows/ci.yaml`](.github/workflows/ci.yaml)) — on push
and PRs to `main`/`dev`, calls
and PRs to `main`/`dev`/`staging`, calls
`Sea-Haven-Industries/.github` → `ci-typescript-frontend.yaml` (Node 24):
format check, lint, build, tests; **and** runs a repo-owned `governance` job
that calls `npm run verify` so every gate (including the maintainability
ratchets in [`scripts/governance-check.mjs`](scripts/governance-check.mjs)) is
guaranteed from this repository. Conventions and gates are documented under
ratchets in [`scripts/governance-check.mjs`](scripts/governance-check.mjs),
the Terraform gates, and the CDK template tests) is guaranteed from this
repository. Conventions and gates are documented under
[`AGENTS.md`](AGENTS.md), [`QUALITY_GATES.md`](QUALITY_GATES.md),
[`ARCHITECTURE_AND_CODE_QUALITY.md`](ARCHITECTURE_AND_CODE_QUALITY.md), and
[`REVIEW_AND_PR_FRAMEWORK.md`](REVIEW_AND_PR_FRAMEWORK.md).
- **CD** ([`.github/workflows/deploy.yml`](.github/workflows/deploy.yml)) — on
push to `dev`, calls `Sea-Haven-Industries/.github` → `cd-cdk.yaml`, which
runs `cdk deploy` on `infra/cdk` (stack `shoc-frontend-dev`, `us-east-1`)
and then [`scripts/deploy-web.sh`](scripts/deploy-web.sh): `npm run build`,
- **Terraform isolation**
([`.github/workflows/terraform-isolation.yaml`](.github/workflows/terraform-isolation.yaml))
— fails a PR that mixes `terraform/**` with application code, so a Terraform
merge never races a content release for the HCP workspace.
- **Dev content** ([`.github/workflows/deploy.yml`](.github/workflows/deploy.yml))
— `workflow_dispatch` on `dev` only while the Terraform adoption is in
progress. Runs `npm run verify`, assumes `githubdeploy-shoc-frontend-new-dev`,
and runs [`scripts/deploy-web.sh`](scripts/deploy-web.sh): `npm run build`,
`aws s3 sync dist/` (hashed assets immutable, `index.html` never cached),
CloudFront invalidation. Both run as the OIDC deploy role.
CloudFront invalidation, then uploads source maps and checks the served
`index.html` matches the build. Push-to-`dev` releases return with the
Terraform content-CD change.
- **Staging content**
([`.github/workflows/deploy-staging.yml`](.github/workflows/deploy-staging.yml))
— on push to `staging`, unchanged.
- **Infrastructure** — administrator-run. Dev: the CDK retain/transfer sequence
and the HCP Terraform workspace `shoc-frontend-new-dev`
([`terraform/README.md`](terraform/README.md)). Staging: `cdk deploy`
([`infra/cdk/README.md`](infra/cdk/README.md)).
One-time provisioning (OIDC provider, CDK bootstrap, first local deploy,
setting `AWS_DEPLOY_ROLE_ARN`) is documented in
[`infra/cdk/README.md`](infra/cdk/README.md).
Manual deploy (emergency/reference only — needs credentials for the
external-dev AWS account; the normal path is push to `dev`):
Manual content deploy (emergency/reference only — needs credentials for the
external-dev AWS account):
```bash
(cd infra/cdk && npx cdk deploy)
STACK_NAME=shoc-frontend-dev AWS_REGION=us-east-1 bash scripts/deploy-web.sh
SITE_BUCKET=seahaven-shoc-frontend-dev CLOUDFRONT_DISTRIBUTION_ID=E2CWLM1AFB964P \
AWS_REGION=us-east-1 bash scripts/deploy-web.sh
```
## Operations
- **Verify:** open <https://dev.seahaven.com> after a green **Deploy** run in
the Actions tab; confirm a deep link (e.g. a work-orders route) loads
directly and API calls succeed.
- **Verify:** open <https://dev.seahaven.com> after a green **Deploy dev
content** run in the Actions tab; confirm a deep link (e.g. a work-orders
route) loads directly and API calls succeed.
- **Logs:** deploy logs live in GitHub Actions (CI + Deploy workflows). There
are no CloudWatch application logs — the stack is static hosting; runtime
errors surface in the browser and on the backend API's side.
- **Common failure modes:**
- _Stale content after deploy_ — the CloudFront invalidation step failed or
is still propagating; re-run the Deploy workflow or invalidate `/*` manually.
- _OIDC `AssumeRole` errors_ — the trust policy is scoped to pushes to `dev`
on this repo; deploys from other branches/repos are rejected by design.
- _OIDC `AssumeRole` errors_ — the trust policy is scoped to the `dev` ref
on this repo; dispatching the workflow from another branch is rejected by
design.
- _Broken API requests after a build_ — `VITE_API_URL` missing the `/api`
suffix or carrying the wrong environment's host (it is baked in at build time).
- _CORS errors_ — the backend must allow the frontend origin; CloudFront does
not proxy `/api`.
- **CI and CD both fire on push to `dev` in parallel** — a red-CI commit still
deploys (matches the org's push-time-CD model; gating deploy on CI is known
follow-up work).
- **Dev has no push-triggered deploy during the adoption.** Merging to `dev`
runs CI only; publish through the **Deploy dev content** workflow. Merging a
`terraform/**` change also queues an HCP Terraform run that a human confirms
or discards (see the operational rules in `terraform/README.md`).
## Documentation
- Infra one-time setup and stack details: [`infra/cdk/README.md`](infra/cdk/README.md)
- Dev Terraform adoption runbook: [`terraform/README.md`](terraform/README.md)
- Rebuild strategy and conventions: [`docs/ARCHITECTURE_PLAN.md`](docs/ARCHITECTURE_PLAN.md);
design system and UI docs under [`docs/`](docs/)