Application Releases Runbook
This runbook covers application releases through Cloudflare Workers Builds, release smoke checks, and failure injection.
Live apps replace this path (P16-15). A Project with a live app (prod) releases by moving its app pointer to the promoted commit’s bundle, then runs the same smoke probes through the apps router. A smoke failure moves the pointer back to the previous version. See ADR 0020, “Previews, releases, versions, rollback”. Workers Builds is legacy: it stays only for the dev release target Acme Shop Live below, which keeps working unchanged.
Principles
Section titled “Principles”A Git push to Cloudflare Artifacts starts each application release. ReleaseWorkflow then verifies the release with executable Evidence (AGENTS.md sections 21.3 and 37, ADR 0018).
Status (2026-10-10): connected. The Workers Builds connection from the Artifacts repo rhumbatron-projects-dev/proj_077b581d-15b3-4f6d-b68a-6a6659cc2668 (Acme Shop Live) to rhumbatron-acme-shop-dev exists. An operator made it in the dashboard (Worker → Settings → Builds → Connect), because the Builds API rejects cloudflare_artifacts request bodies (error 12065).
| Item | Value |
|---|---|
| Trigger UUID | 4fb2e3cf-ac43-4cd3-8107-855a94059a7b |
| Repo connection UUID | c61776d4-d809-44aa-8915-a68f29982ce4 (provider cloudflare_artifacts) |
| Branch | main only; Preview builds are off |
| Build command | none |
| Deploy command | npx wrangler@4.149.0 deploy |
| Build token | rhumbatron acme-shop-dev build token (created by the dashboard, dedicated to this Worker) |
Check it with cf builds triggers list --external-script-id <worker tag>.
1. How Releases Work (ADR 0018)
Section titled “1. How Releases Work (ADR 0018)”Architecture
Section titled “Architecture”sequenceDiagram
participant P as ProjectRootDO
participant A as Artifacts main
participant R as ReleaseWorkflow
participant B as Workers Builds
participant W as rhumbatron-acme-shop-env
P->>A: promotion fast-forwards main
P->>R: start rel_project_generation
A->>B: push triggers build
R->>P: still canonical? (build.started)
loop every 15 s, 30 s, then 60 s, up to 15 min
R->>B: GET builds for the promoted commit
end
B->>W: deploy new version
R->>W: GET deployments (deployment.completed)
R->>W: probe / and /api/products
R->>R: Evidence release-sN to R2 and D1
Note over R: deployed, smoke_failed, failed, or superseded
- Promotion trigger:
- When a Candidate is accepted, promotion fast-forwards the canonical
mainbranch in the Project’s Cloudflare Artifacts repository. - Promotion then starts
ReleaseWorkflowwith instance IDrel_<project>_<generation>. - No direct deploy API calls: Rhumbatron never calls a Workers Builds write API at runtime. The push to
mainstarts Workers Builds directly.
- When a Candidate is accepted, promotion fast-forwards the canonical
- Workers Builds run:
- Terraform manages the release target Worker shell (
cloudflare_worker.acme_shop,rhumbatron-acme-shop-<env>). - Workers Builds owns the Worker’s versions and deployments as runtime data, so Terraform shows no drift.
- Terraform manages the release target Worker shell (
- Workflow observation steps (
ReleaseWorkflowon the integration Worker):- Verify canonical: The workflow confirms that the generation is still canonical in
ProjectRootDO. It sets the state tobuilding(build.started). - Poll builds: The workflow polls
GET /builds/workers/{tag}/buildswith backoff (15 s, 30 s, 60 s; up to 15 minutes). It checks that the build trigger commit matches the promoted commit SHA. - Confirm deployment: The workflow reads the active script deployment version through
GET /workers/scripts/{name}/deployments. It emitsdeployment.completed. - Probe release URL: The workflow probes
/(200),/api/products(200 JSON), and up to two contract routes that the Change added. - Save Evidence: The workflow stores release Evidence in R2 and in D1
evidence_indexunder check IDrelease-s<generation>. - Status transition: On success, the release goes to
deployed. On probe failure, it goes tosmoke_failedorfailed.
- Verify canonical: The workflow confirms that the generation is still canonical in
- Superseding:
- A new generation can be promoted while an older release is
queuedorbuilding. The workflow then marks the older releasesuperseded.
- A new generation can be promoted while an older release is
- Forward-only recovery:
- A failed release never rolls back the Canonical Generation and never deletes history.
- To recover, promote a new generation (a fix or revert Change).
2. One-Time Workers Builds Setup
Section titled “2. One-Time Workers Builds Setup”Connect the Project’s Artifacts repository to Workers Builds once per release Worker (ADR 0014, ADR 0018).
Option A: cf CLI Connection (ADR 0018)
Section titled “Option A: cf CLI Connection (ADR 0018)”cf builds workers create \ --worker rhumbatron-acme-shop-<env> \ --repo-namespace rhumbatron-projects-<env> \ --repo <project-id> \ --branch main \ --deploy-command "npx wrangler@4.149.0 deploy"Option B: Cloudflare Dashboard (ADR 0014)
Section titled “Option B: Cloudflare Dashboard (ADR 0014)”- Go to Worker
rhumbatron-acme-shop-<env>→ Settings → Builds. - Click Connect.
- Choose the Artifacts namespace
rhumbatron-projects-<env>. - Select the Project repository.
- Set the production branch to
main. - Set the deploy command to
npx wrangler@4.149.0 deploy. - Authorize with a dedicated build token.
Runtime Token
Section titled “Runtime Token”ReleaseWorkflow observes builds with the sensitive Terraform variable builds_api_token (rhumbatron-cloudflare-builds-token in Keychain). Terraform binds it as the secret BUILDS_API_TOKEN on the integration Worker. The token has these permissions:
Workers Builds Configuration EditWorkers Scripts Read
The Worker sends only read-only GET requests and never logs the token. The D1 table release_targets holds the target configuration.
3. Release Smoke Check (smoke:release)
Section titled “3. Release Smoke Check (smoke:release)”Run the release smoke check against the deployed release target:
# Smoke check dev:bun run smoke:release
# Smoke check prod (see the note below):bun run smoke:release prod
# Smoke check a specific project:bun run smoke:release dev --project <project-id>- Credentials: The script uses the Clerk smoke user credentials (
CLERK_SECRET_KEYandCLERK_SMOKE_USER_IDfrom Keychain). It loads the dev items (rhumbatron-clerk-*-dev). Prod uses the Clerk production instance. For a prod run, put the prod smoke-user credentials in those variables. - Checks:
release_targetscontains the release target.- The latest release that is not superseded has status
deployed. - D1 contains the deployed
versionId. - R2 and D1 contain the release smoke Evidence object.
- Live HTTP probes against the app shell and the products API return 200 OK.
4. Release Failure Injection (P13-12)
Section titled “4. Release Failure Injection (P13-12)”Simulate a production deployment failure. Canonical Git history stays intact.
bun run inject --only=release-smoke-failure- Mechanism:
- The injection targets the smoke user’s
acme-shop-liveProject on dev. - It calls
POST /v1/projects/:projectId/releases/inject-failure(enabled byFIXTURES_ENABLED = "true"). - It probes the live release URL with the standard probes plus the missing route
/__rhumbatron/injected-missing-route. - The HTTP 404 starts the standard failure path. The release becomes
smoke_failed, Evidencerelease-s<N>-injectedgoes to R2, and the eventrelease.smoke_failed(injected: true) is emitted.
- The injection targets the smoke user’s
- Invariants kept:
- The Canonical Generation does not change and does not roll back.
- All release and Change history stays.
- To recover, write and promote the next generation
S<N+1>.