Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 14 additions & 3 deletions RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.

Expand All @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions docs/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions internal/handler/changelog.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading