Claim
The release lane gates on five live smoke assertions: a build that misses one is rolled back to the previous release, and deploy.log records the order.
Eight PR gates feed one required ci-ok check; the local release lane builds, swaps, waits for health, proves five smoke assertions, and records a rollback.
01 / What it is
What it is
A Fastify invoice API on PostgreSQL 16 with a GitHub Actions workflow, a Dockerfile and a local release script.
The workflow defines eight gates and a ci-ok aggregator; the release script swaps a current pointer, waits on /health and /ready, runs five smoke assertions and rolls back to the previous release when an assertion misses.
02 / The hard part
The hard part
A process can answer /health and /ready while its invoice route returns errors, and a release with no return path leaves that process in front of traffic. The hard part is deciding the deploy on what the service does, not on what its health endpoint reports.
03 / The decision
The decision
Smoke decides the release; health starts the wait
Decision After the health wait, run five smoke assertions and roll back when any assertion misses, then re-smoke the restored release.
Rejected alternative Treat healthy /health and /ready responses as success; a process can answer both while its invoice route returns errors.
Cost Every release pays for five live assertions and a rollback restart, and rollback returns to the previous release.
Evidence deploy.log records deploy.start, health.ok, smoke.fail, rollback.start, rollback.health.ok, rollback.smoke.ok.
04 / The failure it refuses
The failure it refuses
The process answers /health and /ready while the invoice route returns errors, or a release is promoted with no way back to the previous build.
Mechanism The release lane swaps the current pointer, waits on /health and /ready, then runs five smoke assertions against the live service; any miss triggers rollback.start, restores the previous release, and re-runs the same smoke gate. deploy.log records every step in order.
05 / Measured
Measured
The local lane is measured: nine integration tests passed, the smoke gate passed five of five assertions with /ready p95 7.7 ms, and deploy.log records the rollback order. The workflow structure check found eight gates feeding ci-ok and zero unpinned actions.
The workflow is the source of the gate topology; the local lane is the source of the runtime evidence.
| Metric | Value | Method | Artifact |
|---|---|---|---|
| Rollback ordering | deploy.start → health.ok → smoke.fail → rollback.start → rollback.health.ok → rollback.smoke.ok | read deploy.log in file order; elapsed seconds computed from the recorded timestamps | deploy.log |
| Image scan | 0 CRITICAL/HIGH findings across 68 targets with ignore-unfixed; exit 0 | trivy image --severity CRITICAL,HIGH --ignore-unfixed --exit-code 1 invoice-api:slim | docs/evidence/trivy-local.txt |
| Workflow structure | 9 jobs including ci-ok; 0 unpinned uses | node structural check over .github/workflows/pr.yml | docs/evidence/workflow-structure.txt |
| Docker daemon | Docker 28.1.1, linux/x86_64; the image was built and scanned locally | docker info | docs/evidence/docker-info-latest.txt |
Gate checks (test counts, not metrics)
- Integration tests: 9/9 integration tests passed; verify: ok; exit 0. bash scripts/verify.sh (eslint, tsc --noEmit, vitest run)
docs/evidence/verify-latest.txt - Smoke gate: 2 runs passed 5/5 smoke assertions (healthy release and rollback); /ready p95 23.1 ms. bash scripts/demo.sh against the local release on 127.0.0.1:8787
docs/evidence/demo.txt
Invariants and their tests
- Unauthenticated POST /invoices is rejected with 401; an authenticated request returns 201 with a UUID id
tests/integration/api.test.ts - GET /ready reports 503 pending_migrations when a migration is unapplied, and 200 when database and migrations are okay
tests/integration/api.test.ts - An unknown invoice id returns 404 not_found with no stack trace in the body
tests/integration/api.test.ts