docs: carlos deploy, not ship; and check the process, not the edge
Paul's ruling: use `carlos deploy`, never `carlos ship` alone. `ship` publishes a release and stops — it sits unpromoted, no channel points at it, nothing serves it. I hit exactly that on the first deploy of this app and had to promote by hand afterwards. Also corrects a rule in this file that was wrong and would have walked the next person into the trap it was meant to prevent. It named X-Carlos-Version as the proof of a deploy. That header is the router's ADOPTED RELEASE: it flips the instant the promote lands and says nothing about which binary is running. GET /api/version is served by the process itself. I found the difference by shipping an unstamped binary — the header said the new sha while the process said "dev" — which is also the only reason I noticed the process had not recycled at all. The last line is the part worth keeping: unstamped, the two facts cannot disagree, so a check comparing them has no failing case. It passes forever and proves nothing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01APtNStXDZ5H2fAc1ZuGqtS
1 file changed,
+20
−3
AGENTS.md+20 −3
CI passed — run details
Intent
diff --git a/AGENTS.md b/AGENTS.md| index 8a06b74..2af266a 100644 |
| --- a/AGENTS.md |
| +++ b/AGENTS.md |
| @@ -187,10 +187,27 @@ relying on remembering one, make it a test instead. |
| - One binary, `CGO_ENABLED=0`, cgo-free SQLite. A cgo driver sneaking in is |
| a bug. |
| - Review on a deployed canary, never localhost. |
| -- A 200 is not a deploy verification. Only `X-Carlos-Version` on the |
| - canonical host, after the watch, is proof. |
| +- **Deploy with `carlos deploy`, never `carlos ship` on its own.** `ship` |
| + publishes a release and stops there: the release sits unpromoted, no |
| + channel points at it, and nothing serves it. `deploy` ships, promotes |
| + and waits for the URL — one command, and it is the one that tells you |
| + when it is actually live. (Paul, 2026-08-31.) |
| +- **Build shipping binaries with `make release`, never a bare `go build`.** |
| + It stamps `rastrillo.BuildVersion` via `-ldflags -X` and refuses a dirty |
| + tree. An unstamped binary reports `dev` from `/api/version` forever. |
| +- A 200 is not a deploy verification, and neither is `X-Carlos-Version`. |
| + That header is the **router's adopted release**; it flips the moment the |
| + promote lands and says nothing about which binary is running. `GET |
| + /api/version` is served by the process itself. Check the process, not |
| + the edge. |
| - A promote is not a deploy. Until the process cycles, the old binary |
| - serves. |
| + serves — measured at over 90 seconds here on three consecutive deploys, |
| + and about ten on another app, so poll rather than sample. A single early |
| + read looks like a failed deploy; a single read against an unstamped |
| + binary reports `dev` and looks like a successful one. |
| +- The two facts only disagree when the binary is stamped. Unstamped, they |
| + are structurally incapable of disagreeing, so a check comparing them has |
| + no failing case and proves nothing while passing. |
| --- |