diff --git a/QUALITY_GATES.md b/QUALITY_GATES.md index 62ae26c5..75693239 100644 --- a/QUALITY_GATES.md +++ b/QUALITY_GATES.md @@ -51,7 +51,7 @@ isolation). A task is not done until this is green. are migration evidence reviewed by a human before an approved apply (`terraform/README.md`). G13 fails a diff that contains both `terraform/` and deployable application files (`src/`, `public/`, `pages/`, `config/`, - `index.html`, Vite/tsconfig, `.env*`, or `scripts/deploy-web.sh`). Workflow, + `index.html`, Vite/tsconfig, or `.env*`). Workflow, docs, and gate-script changes may travel with either side. Runtime isolation stays: `deploy-web.yaml` ignores `terraform/**`, and app-only tags skip HCP when workspace trigger patterns miss. diff --git a/REVIEW_AND_PR_FRAMEWORK.md b/REVIEW_AND_PR_FRAMEWORK.md index 68b4b07b..911e8042 100644 --- a/REVIEW_AND_PR_FRAMEWORK.md +++ b/REVIEW_AND_PR_FRAMEWORK.md @@ -87,6 +87,6 @@ surface; keep comments inline and high-signal. Infra and application **PRs** stay separate. GitHub Actions owns SPA content (`deploy-web.yaml`). HCP Terraform owns the bucket and CloudFront. A change set that includes both `terraform/` and deployable application files (`src/`, -`public/`, `pages/`, `config/`, `index.html`, Vite/tsconfig, `.env*`, or -`scripts/deploy-web.sh`) fails G13. Workflow, docs, and gate-script changes may -travel with either side. +`public/`, `pages/`, `config/`, `index.html`, Vite/tsconfig, or `.env*`) +fails G13. Workflow, docs, and gate-script changes may travel with either +side. diff --git a/scripts/check_app_terraform_isolation.py b/scripts/check_app_terraform_isolation.py index 13266963..72dff249 100644 --- a/scripts/check_app_terraform_isolation.py +++ b/scripts/check_app_terraform_isolation.py @@ -9,10 +9,6 @@ from __future__ import annotations import argparse import sys -APP_SCRIPT_NAMES = { - "scripts/deploy-web.sh", -} - APP_ROOTS = ( "src/", "public/", @@ -33,7 +29,7 @@ def is_terraform_path(path: str) -> bool: def is_app_path(path: str) -> bool: normalized = path.replace("\\", "/") - if normalized in APP_SCRIPT_NAMES or normalized in APP_FILES: + if normalized in APP_FILES: return True if normalized in {"src", "public", "pages", "config"}: return True diff --git a/scripts/deploy-web.sh b/scripts/deploy-web.sh deleted file mode 100755 index ec64a742..00000000 --- a/scripts/deploy-web.sh +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env bash -# -# Content publish step for the environment deploy workflows -# (`.github/workflows/deploy.yml`, `.github/workflows/deploy-staging.yml`). -# -# Runs as the GitHub OIDC deploy role. Builds the SPA, uploads it to the -# environment's S3 bucket with the right cache headers, and invalidates -# CloudFront. It never touches infrastructure. -# -# Runs from the repo root. The target is resolved from, in order: -# 1. SITE_BUCKET + CLOUDFRONT_DISTRIBUTION_ID (pinned by the workflow; used by -# dev, whose CloudFormation outputs disappear during Terraform adoption) -# 2. the BucketName/DistributionId outputs of STACK_NAME (staging) -set -euo pipefail - -STACK_NAME="${STACK_NAME:-shoc-frontend-dev}" -REGION="${AWS_REGION:-us-east-1}" -WAIT_FOR_INVALIDATION="${WAIT_FOR_INVALIDATION:-false}" - -echo "Building SPA (VITE_API_URL comes from the process environment or .env.production)..." -export VITE_APP_COMMIT_SHA="${VITE_APP_COMMIT_SHA:-${GITHUB_SHA:-}}" -npm ci -npm run build - -BUCKET="${SITE_BUCKET:-}" -DIST_ID="${CLOUDFRONT_DISTRIBUTION_ID:-}" - -if [[ -n "${BUCKET}" && -n "${DIST_ID}" ]]; then - echo "Using pinned target: bucket ${BUCKET}, distribution ${DIST_ID}." -elif [[ -n "${BUCKET}" || -n "${DIST_ID}" ]]; then - echo "::error::Set both SITE_BUCKET and CLOUDFRONT_DISTRIBUTION_ID, or neither." >&2 - exit 1 -else - echo "Reading stack outputs from ${STACK_NAME}..." - stack_output() { - aws cloudformation describe-stacks \ - --stack-name "${STACK_NAME}" \ - --region "${REGION}" \ - --query "Stacks[0].Outputs[?OutputKey=='$1'].OutputValue" \ - --output text - } - - BUCKET="$(stack_output BucketName)" - DIST_ID="$(stack_output DistributionId)" - - if [[ -z "${BUCKET}" || "${BUCKET}" == "None" || -z "${DIST_ID}" || "${DIST_ID}" == "None" ]]; then - echo "::error::Could not resolve BucketName/DistributionId from stack ${STACK_NAME}." >&2 - exit 1 - fi -fi - -echo "Uploading hashed assets (immutable) to s3://${BUCKET}..." -# Everything except index.html: long-lived + immutable, prune stale objects. -aws s3 sync dist/ "s3://${BUCKET}/" \ - --delete \ - --exclude "index.html" \ - --exclude "*.map" \ - --cache-control "public,max-age=31536000,immutable" - -echo "Uploading index.html (never cached)..." -aws s3 cp dist/index.html "s3://${BUCKET}/index.html" \ - --cache-control "no-cache,no-store,must-revalidate" \ - --content-type "text/html" - -echo "Invalidating CloudFront ${DIST_ID}..." -INVALIDATION_ID="$(aws cloudfront create-invalidation \ - --distribution-id "${DIST_ID}" \ - --paths "/*" \ - --query 'Invalidation.Id' \ - --output text)" - -if [[ "${WAIT_FOR_INVALIDATION}" == "true" ]]; then - echo "Waiting for CloudFront invalidation ${INVALIDATION_ID}..." - aws cloudfront wait invalidation-completed \ - --distribution-id "${DIST_ID}" \ - --id "${INVALIDATION_ID}" -fi - -echo "Web deploy complete." diff --git a/scripts/read-release-pointer.py b/scripts/read-release-pointer.py deleted file mode 100755 index d570310f..00000000 --- a/scripts/read-release-pointer.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python3 -"""Read .release/current JSON from stdin and write GitHub Actions outputs.""" -from __future__ import annotations - -import json -import os -import sys - - -def main() -> int: - raw = sys.stdin.read().strip() - data = json.loads(raw) if raw else {} - current = data.get("current") or "" - previous = data.get("previous") or "" - output_path = os.environ["GITHUB_OUTPUT"] - with open(output_path, "a", encoding="utf-8") as handle: - handle.write(f"live_current={current}\n") - handle.write(f"live_previous={previous}\n") - print( - "Pointer live current=" - + (current or "") - + " previous=" - + (previous or "") - ) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/test_check_app_terraform_isolation.py b/scripts/test_check_app_terraform_isolation.py index 22ba06ac..661f10de 100644 --- a/scripts/test_check_app_terraform_isolation.py +++ b/scripts/test_check_app_terraform_isolation.py @@ -26,7 +26,6 @@ class IsolationTests(unittest.TestCase): "src/app/routes.tsx", "public/favicon.ico", "index.html", - "scripts/deploy-web.sh", ] ) ) diff --git a/terraform/README.md b/terraform/README.md index 8bfd6bcb..a2922962 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -7,10 +7,10 @@ to the bucket root and invalidates `/*`. Creating, formatting, initializing with `-backend=false`, and validating these files does not authorize an AWS, HCP Terraform, GitHub, or deployment -mutation. Live cutover waits for an explicit greenlight. +mutation. -Do not collapse these roots into one `terraform/` tree in this PR. Flattening -retargets two live HCP working directories and is its own change. +Do not collapse these roots into one `terraform/` tree. Flattening retargets +two live HCP working directories and is its own change. ## Fixed targets @@ -61,15 +61,14 @@ and gate-script changes may travel with either side. G13 is `npm run test:terraform` and `npm run verify` wrap the same gates. They never create an HCP run or touch AWS. -## Cutover (greenlight only) +## Workspaces -1. Keep HCP working directories `terraform/live/dev` and - `terraform/live/staging`. Dev VCS branch `main`. Staging tag regex - `^v[0-9]+\.[0-9]+\.[0-9]+-staging$`. -2. Auto-apply off. Apply the origin-path move for **dev** before any - `--delete` root sync. -3. Create GitHub Environment `dev` (staging already exists). Set - `DEPLOY_ROLE_ARN` on each. -4. Enable `deploy-web.yaml`. Then delete leftover `deploy.yml` / - `deploy-staging.yml` and repo `TF_API_TOKEN`, `TERRAFORM_CONTENT_CD_ENABLED`, - and `AWS_DEPLOY_ROLE_ARN`. +Dev (`shoc-frontend-new-dev`) watches `main` with working directory +`terraform/live/dev` and auto-apply on. Staging (`shoc-frontend-new-staging`) +watches tag regex `^v[0-9]+\.[0-9]+\.[0-9]+-staging$` with working directory +`terraform/live/staging` and auto-apply on. Merges to `main` do not apply +staging. + +GitHub Environments `dev` and `staging` set `DEPLOY_ROLE_ARN` and allow `main` +plus tag `v*`. Promote staging with `gh release create vX.Y.Z-staging --target +main`.