oficina / sheets Public

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
Paul Campbell pushed by paul@keymail.dev 359c37fc33951685dd88f8e3a2ff635d05b146af parent f05b7e1
1 file changed, +20 −3
  • AGENTS.md +20 −3

CI passed — run details

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.
---