diff --git a/claude.md b/claude.md index e8555ad..3d05e66 100644 --- a/claude.md +++ b/claude.md @@ -112,11 +112,15 @@ Full details in `docs/DEPLOY.md`. The short version: - **Never push to `main` directly.** Work on a feature branch and open a PR; branch protection requires the PR checks (typecheck, tests, builds, AI review) to pass before merge. -- Merging to `main` deploys automatically: images are built once, deployed to - the **staging** Portainer stack, verified by the Playwright journeys in - `e2e/`, and only then retagged `:prod` and rolled out to production. +- Merging to `main` deploys automatically **to staging only**: images are + built once, deployed to the staging Portainer stack, and verified by the + Playwright journeys in `e2e/`. Production is a second, manual approval: + the "Promote to Production (manual)" workflow in Gitea Actions, run after + testing the feature on staging. It refuses commits whose staging E2E gate + isn't green. Never trigger it yourself — promotion is the human's call. - If you change user-facing behaviour, update or extend the `e2e/` journey - tests in the same PR — they are the promotion gate. + tests in the same PR — they gate whether staging is fit for human testing + and whether a commit is promotable. ## Recent Changes diff --git a/docs/DEPLOY.md b/docs/DEPLOY.md index 1088fac..02123f6 100644 --- a/docs/DEPLOY.md +++ b/docs/DEPLOY.md @@ -1,41 +1,61 @@ # SDLC & Deployment Pipeline -SchoolCompare uses a fully automated staging → production pipeline on Gitea -Actions. AI writes the code on feature branches; the pipeline verifies every -change on a staging environment before promoting the exact same images to -production. Human input is directional only: feature requests, PR review if -desired, and intervention when a gate fails. +SchoolCompare uses a two-stage deploy model on Gitea Actions with two human +approvals. AI writes the code on feature branches; the first approval merges +the PR, which deploys to staging and runs the E2E gate; the second approval — +after manual testing on staging — promotes the exact same images to +production via a manual workflow. ## The flow ``` feature branch (AI-authored) - │ PR to main + │ PR to main ← approval #1 ▼ PR checks (.gitea/workflows/pr-checks.yml) typecheck + unit tests + backend smoke + image builds (no push) + Claude code review posted as a PR comment (severe findings fail the check) │ merge (branch protection requires green checks) ▼ -Deploy pipeline (.gitea/workflows/deploy.yml) +Stage pipeline (.gitea/workflows/deploy.yml) — automatic 1. build & push images → tags sha-, staging 2. staging Portainer webhook → wait for staging health - 3. Playwright E2E journeys against staging - 4. retag sha- → :prod (same bytes — build once, promote the image) + 3. Playwright E2E journeys against staging ← gate before human testing + ▼ +Manual testing on staging (stx.schoolcompare.co.uk) + │ Actions → "Promote to Production (manual)" ← approval #2 + ▼ +Promote pipeline (.gitea/workflows/promote.yml) — manual dispatch + 1. resolve target sha (input, or latest main if empty) + 2. REFUSE unless that commit's "E2E Journeys against Staging" status is green + 3. retag sha- → :prod (same bytes — build once, promote the image) previous :prod saved as :prod-previous - 5. prod Portainer webhook → wait for prod health + 4. prod Portainer webhook → wait for prod health ``` Key principle: **build once, promote the exact image**. Production pins `:prod`, -which only moves after the E2E gate passes on staging. Nothing tags `:latest` +which only moves when a human runs the promote workflow — and the workflow +only accepts commits that passed the staging E2E gate. Nothing tags `:latest` anymore. ## Branch & PR workflow - `main` is protected: no direct pushes, PRs require green status checks. - All work (human or AI) happens on feature branches → PR to `main`. -- Merging to `main` **is** the release action. If staging or the E2E gate - fails, production is untouched. +- Merging to `main` releases **to staging only**. Production moves only on + the second approval. If staging or the E2E gate fails, fix forward — + production is untouched either way. + +## Promotion granularity + +Staging always runs the latest `main`. Promoting approves a *state of main*, +not a single PR — if two PRs merged since the last promotion, they ship +together. Test staging accordingly. To promote an older state, pass its +commit SHA to the promote workflow (its images must still exist in the +registry). + +Staging quirk for manual testing: external `/api` is broken at the staging +proxy — exercise API endpoints from the host, not via the public staging URL. ## Environments @@ -92,8 +112,9 @@ fail the E2E gate. That's the point: staging absorbs the risk. ## Rollback -Every promotion first re-points `:prod-previous` at the outgoing `:prod`. -To roll back: +Re-run "Promote to Production (manual)" with the SHA of the last good commit +(fastest, fully gated), or manually re-point the tags — every promotion first +saves the outgoing `:prod` as `:prod-previous`: ```bash for img in backend frontend pipeline; do