diff --git a/AGENTS.md b/AGENTS.md index 27b34c5..3fc5d74 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -79,8 +79,9 @@ open the file. ## Conventions - Feature branch → PR into `main`. No direct pushes to `main`. Open the PR, do not merge - it. A release is a `v*` tag on `main`, and the tag is the deploy — see `RELEASE.md`. - Never push one except through `make release`. + it. A release is a `v*` tag on `main`, and the tag is the deploy, gated on Jason + approving the `production` environment — see `RELEASE.md`. Never push one except + through `make release`, and never approve a deployment. - **Every PR merges as a merge commit. Never squash, never rebase.** Squash and rebase replace the branch's commits with new ones: SHAs referenced in comments and changelogs stop existing, and the `Assisted-by:` trailers the fleet audit reads get flattened into diff --git a/RELEASE.md b/RELEASE.md index 43ceab7..fc6317a 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -11,8 +11,13 @@ tag; `.github/workflows/fly-deploy.yml` fires on any `v*` tag, runs the test wor and then `flyctl deploy` with the tag name baked in as the version. There is no `release` branch and no second PR. -The human decision is `make release`. Everything before it has already been reviewed on -its way into `main`; everything after it is automatic. +The deploy job runs in the `production` GitHub environment, which requires a reviewer +(set 2026-10-17). After the tests pass, the run pauses at "Review deployments" in the +Actions UI until Jason approves it; nothing reaches Fly before that click. An agent +reports the waiting run and stops. It never approves a deployment, by `gh` or otherwise. + +The human decisions are `make release` and that approval. Everything before the tag has +already been reviewed on its way into `main`; everything after the approval is automatic. ## Preconditions @@ -48,6 +53,11 @@ Any of these failing stops the release. Do not release around a dirty tree. 3. **`make release V=vX.Y.Z`.** Creates an annotated tag and pushes it. **This is the deploy.** Report the version and stop; the rest is the pipeline's. +4. **Approve the deployment.** Once the tests pass, the run waits on the `production` + environment reviewer. Jason approves it under Actions → the run → Review deployments. + An agent waiting on a release reports "waiting for approval" with the run URL and does + nothing else until the run finishes. + Branch names must not start with `release/` — git cannot hold a `release` ref and a `release/…` ref at once, and the old branch may still exist in some clones. @@ -73,7 +83,8 @@ mismatch here means the deploy did not happen, not that it was mislabelled. Redeploy an earlier tag: **Actions → Fly Deploy → Run workflow → tag** (or `gh workflow run fly-deploy.yml -f tag=vX.Y.Z`). The workflow refuses anything that is not -exactly a release tag. Then run the Verify section against that tag. +exactly a release tag. A rollback goes through the same `production` approval as a +release. Then run the Verify section against that tag. A rollback is a deploy, not a release: no changelog entry, no new tag. If the fix is a code change, that is a new release. diff --git a/docs/operations.md b/docs/operations.md index 98f98e5..c2d4d11 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -230,7 +230,7 @@ The API is deployed on **Fly.io** with **Neon** (PostgreSQL) and **Upstash** (Re - **Pull requests** — `test.yml` runs gofmt, vet, lint on new findings, unit tests with `-race`, and integration tests - **Push to `main`** — `docker.yml` builds and pushes an image to GHCR -- **`v*` tag** — `fly-deploy.yml` runs the tests, builds the image with the version baked in, and deploys it to Fly.io. **The tag is the deploy.** Re-running it with `workflow_dispatch` and an existing tag redeploys that version, which is the rollback path +- **`v*` tag** — `fly-deploy.yml` runs the tests, builds the image with the version baked in, and deploys it to Fly.io. **The tag is the deploy.** The deploy job runs in the `production` GitHub environment, which requires a reviewer, so the run pauses at "Review deployments" after the tests until it is approved. Re-running it with `workflow_dispatch` and an existing tag redeploys that version, which is the rollback path, through the same approval - **Uptime monitor** — `uptime.yml` pings `/v1/health` every 5 minutes and sends a Pushover alert on failure Every third-party action is pinned to a commit SHA, with the version in a trailing comment. Dependabot opens a PR when a pinned action has a new release. @@ -240,7 +240,7 @@ Every third-party action is pinned to a commit SHA, with the version in a traili A release is a `v*` tag on `main`, created with `make release`. The full procedure, including the changelog entry and the rollback path, is in `RELEASE.md`. ```sh -make release V=v0.X.Y # tag main and push the tag; the tag is the deploy +make release V=v0.X.Y # tag main and push the tag; the tag is the deploy, gated on the production reviewer ``` ### Environment variables (Fly secrets) diff --git a/internal/handler/changelog.yaml b/internal/handler/changelog.yaml index 46a138f..1499db0 100644 --- a/internal/handler/changelog.yaml +++ b/internal/handler/changelog.yaml @@ -10,6 +10,12 @@ # terms (#91). An entry a later release reversed keeps its text and gains a # "superseded in vX" note, rather than being hidden or rewritten. +- version: v1.0.1 + date: 2026-10-17 + changes: + - text: "The `production` GitHub environment requires a reviewer, so a `v*` tag runs the tests and then waits at \"Review deployments\" until Jason approves it; nothing reaches Fly before that. RELEASE.md, docs/operations.md and AGENTS.md describe the gate, and an agent reports the waiting run rather than approving it" + audience: internal + - version: v1.0.0 date: 2026-10-17 changes: