spec: zero-downtime deploys rev 11 — answers Astra round 10 (no restage over referenced builds, captured worker identity)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 files changed,
+58
−9
docs/superpowers/specs/2026-10-07-zero-downtime-deploys-design.md+36 −9docs/superpowers/specs/CODEX-FINDINGS-ZERO-DOWNTIME-R10.md+22 −0
CI passed — run details
diff --git a/docs/superpowers/specs/2026-10-07-zero-downtime-deploys-design.md b/docs/superpowers/specs/2026-10-07-zero-downtime-deploys-design.md| index 7e09dc282..05bee5e4c 100644 |
| --- a/docs/superpowers/specs/2026-10-07-zero-downtime-deploys-design.md |
| +++ b/docs/superpowers/specs/2026-10-07-zero-downtime-deploys-design.md |
| @@ -1,6 +1,6 @@ |
| # Zero-downtime deploys: overlap swaps and paced rollout |
| -Status: draft rev 10, 2026-10-07. Discussions #231 (deploy pickup) and #230 |
| +Status: draft rev 11, 2026-10-07. Discussions #231 (deploy pickup) and #230 |
| (per-app restart pace, first-route health step) feed into this. Astra |
| rejected rev 1 with 17 findings, rev 2 with 12 new ones, rev 3 with 7, rev 4 |
| with 3 and rev 5 with 4. The findings files are |
| @@ -129,11 +129,21 @@ directory without comparing it with the canonical artifact |
| failure record against B. It is reported, and the stage relay is |
| touched. |
| - **Root's staging** checks a reused staged directory's digest against |
| - the canonical manifest and restages on a mismatch, so the next pass |
| - repairs it. |
| - |
| -A good build that is still staged was verified when it last ran, and |
| -fallback runs those same bytes (§8.2). |
| + the canonical manifest. On a mismatch, what happens depends on whether |
| + anything on the box still refers to the staged copy. That is the |
| + version sweep's pin set (§8.5): live links, `Version`, `PrevVersion`, |
| + `GoodVersion`, and every live or pending generation. |
| + - **Nothing refers to it:** root restages over it, as today. |
| + - **Something refers to it:** root **refuses to overwrite** it. Staged |
| + paths are keyed by version name with no digest |
| + (`internal/release/versions.go`), so overwriting would change the |
| + bytes under another instance's good build or running generation. The |
| + install of the reused name is refused on that box, and reported as |
| + "version name reused with different content, ship under a new name". |
| + |
| +So a staged copy that something refers to never changes underneath it. |
| +Fallback runs exactly the bytes that passed that instance's health check |
| +(§8.2). |
| **If the canonical manifest can't be read, or the digests can't be |
| compared, the attempt runs in `hold`.** Missing or unverifiable policy |
| @@ -483,9 +493,13 @@ build is never held by a failure record, because it has already served |
| on that host (§8.4). |
| For a sidecar, the **installed build** is what root stamped. The |
| -**serving build** is the version the running worker reports. These are |
| -kept apart: the worker-reported version is recorded as serving and no |
| -longer overwrites registry `Version`. |
| +**serving build** is the build the agent itself recorded when it spawned |
| +the worker: the staged path and digest captured at exec. Recycling, |
| +dedupe, failure matching and `GoodVersion` all use that recorded build. |
| + |
| +The version a worker reports over the agent socket can be empty or an |
| +app-chosen string (`internal/agentsock/agentsock.go`). It is kept as |
| +**display metadata only**, and no longer overwrites registry `Version`. |
| ### 7.10 Scheduled work during an overlap |
| @@ -770,6 +784,12 @@ keep timer names stable across the two builds. Otherwise deploy with |
| - **Artifact binding:** a re-shipped version name with different bytes, |
| plus an old staged copy, refuses the attempt as a staging error, and |
| root restages. An unverifiable digest runs in `hold`. |
| +- **Restage refusal:** a reused version name with different bytes is not |
| + restaged over a copy that a good build or a live generation refers to. |
| + It is refused and reported. An unreferenced stale copy is restaged. |
| +- **Worker identity:** a worker reporting an empty or app-chosen version |
| + still recycles onto its captured spawn build, and records `GoodVersion` |
| + from that build. |
| - **Worker recycling:** a crash or `max_run` recycle during another |
| worker's canary respawns the serving build, not the installed one. |
| `swap_concurrency=1` holds across recycles. A recycle that is admissible |
| @@ -979,3 +999,10 @@ answer for each. |
| |---|---------|--------| |
| | 1 | Canonical policy can describe different bytes from the staged executable | §4.2: compare the staged digest with the canonical artifact digest before any attempt; a mismatch is a staging error; root restages on a mismatch; unverifiable → `hold`. Corrects the rev-8 claim that the pointer names a manifest hash | |
| | 2 | Worker crash and `max_run` respawns bypass pacing | §7.9: recycles respawn the serving build by immutable path inside the operation set, and coalesce with an admissible queued attempt | |
| + |
| +### Round 10 |
| + |
| +| # | Finding | Answer | |
| +|---|---------|--------| |
| +| 1 | Restaging can overwrite another instance's good build | §4.2: a mismatched staged copy that anything refers to is never overwritten; the reused name is refused on that box and reported | |
| +| 2 | A worker's reported version can't identify its executable | §7.9: serving build = the agent-captured spawn build; the reported version is display metadata only | |
diff --git a/docs/superpowers/specs/CODEX-FINDINGS-ZERO-DOWNTIME-R10.md b/docs/superpowers/specs/CODEX-FINDINGS-ZERO-DOWNTIME-R10.md| new file mode 100644 |
| index 000000000..456479dee |
| --- /dev/null |
| +++ b/docs/superpowers/specs/CODEX-FINDINGS-ZERO-DOWNTIME-R10.md |
| @@ -0,0 +1,22 @@ |
| +REJECT |
| + |
| +R9-1: **CLOSED** — The digest comparison and mismatch refusal address the reported stale-policy mismatch. |
| +R9-2: **CLOSED** — Crash and `max_run` recycling explicitly preserve the serving build and coalesce with admitted release attempts. |
| + |
| +No split/removal regression found in fallback without repointing, channel-installed failure holds, cold sleepers, `GoodVersion` versus `PrevVersion`, or stage-trigger retransmission. Revision 10’s new restaging rule creates a separate fallback regression below. |
| + |
| +1. **HIGH — §§4.2, 7.5, 8.2, 8.5: Restaging can overwrite another instance’s good build.** |
| + |
| + **Code:** [`internal/release/versions.go:53`](/home/paulca/amadan.net/carlos/platform-design-zero-downtime-deploys/internal/release/versions.go:53) keys the executable path by account, app and version name, without a digest. [`versions.go:126`](/home/paulca/amadan.net/carlos/platform-design-zero-downtime-deploys/internal/release/versions.go:126) explicitly defines restaging as overwriting; the replacement uses atomic rename at [`versions.go:119`](/home/paulca/amadan.net/carlos/platform-design-zero-downtime-deploys/internal/release/versions.go:119). Re-shipping different bytes under a pruned name is permitted at [`internal/release/release.go:632`](/home/paulca/amadan.net/carlos/platform-design-zero-downtime-deploys/internal/release/release.go:632). |
| + |
| + **Failure:** H1 retains `GoodVersion=A` while its installed build advances. After bucket pruning, A is re-shipped with different bytes and promoted for H2. Following §4.2, root detects the mismatch and replaces the shared staged A executable. H1’s subsequent fallback executes the replacement, although it never passed H1’s health check and may require `hold`. Pinning the version directory against sweeping does not prevent this overwrite. The assertion that fallback runs the same previously verified bytes is therefore false. |
| + |
| + **Smallest fix:** Refuse conflicting restages over artifacts referenced by good builds, generations or pending attempts, requiring a new version name. If automatic repair must remain, stage each digest separately and persist that immutable artifact identity with generations and good-build records. |
| + |
| +2. **MEDIUM — §§6.5, 7.9, 8.5: A worker’s reported version cannot identify its respawn executable.** |
| + |
| + **Code:** [`internal/agentsock/agentsock.go:189`](/home/paulca/amadan.net/carlos/platform-design-zero-downtime-deploys/internal/agentsock/agentsock.go:189) explicitly permits an empty version and treats it as an untrusted claim. [`activator.go:2024`](/home/paulca/amadan.net/carlos/platform-design-zero-downtime-deploys/internal/activator/activator.go:2024) records that value separately from the spawn version captured at [`backend_exec.go:1363`](/home/paulca/amadan.net/carlos/platform-design-zero-downtime-deploys/internal/activator/backend_exec.go:1363). |
| + |
| + **Failure:** A sidecar executing A reports `version:""` or an application revision that differs from its release name, then passes readiness. Section 7.9 defines this reported value as its serving build and requires automatic recycling to execute that build’s staged path. On a crash or `max_run`, there is no executable under that name, so a previously healthy worker cannot respawn. |
| + |
| + **Smallest fix:** Use the agent-captured executable identity for recycling, deduplication, failure matching and `GoodVersion`. Keep the worker-reported version as separate display metadata. |
| \ No newline at end of file |