spec: instance update settings rev 7 — S5 fix applied (an unresolved request never blocks recovery); accepted by Astra
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 files changed,
+39
−5
docs/superpowers/specs/2026-10-07-instance-update-settings-design.md+22 −5docs/superpowers/specs/CODEX-FINDINGS-SETTINGS-S5.md+17 −0
CI passed — run details
diff --git a/docs/superpowers/specs/2026-10-07-instance-update-settings-design.md b/docs/superpowers/specs/2026-10-07-instance-update-settings-design.md| index 8c457c3df..39aa0dcbe 100644 |
| --- a/docs/superpowers/specs/2026-10-07-instance-update-settings-design.md |
| +++ b/docs/superpowers/specs/2026-10-07-instance-update-settings-design.md |
| @@ -1,6 +1,6 @@ |
| # Instance update settings: auto-update, channel, major hold, pin |
| -Status: draft rev 6, 2026-10-07. Split out of the zero-downtime deploys |
| +Status: rev 7, 2026-10-07. **Accepted by Astra** (S5: accept with fixes, the one fix applied in rev 7). Awaiting Paul's review. Split out of the zero-downtime deploys |
| spec (`2026-10-07-zero-downtime-deploys-design.md`) at its rev 6. Rev 1 was |
| a design with console-signed per-instance directives. Astra's round 6 |
| (`CODEX-FINDINGS-ZERO-DOWNTIME-R6.md`) rejected it with 6 findings. |
| @@ -18,7 +18,9 @@ review of rev 3 (`CODEX-FINDINGS-SETTINGS-S2.md`) closed two of those five |
| and left three partial. Rev 4 answered those three and one new finding |
| (§10). S3 (`CODEX-FINDINGS-SETTINGS-S3.md`) closed one and left three |
| partial. Rev 5 answered them (§11). S4 (`CODEX-FINDINGS-SETTINGS-S4.md`) |
| -closed two and left two partial. Rev 6 answers them (§12). |
| +closed two and left two partial. Rev 6 answered them (§12). S5 |
| +(`CODEX-FINDINGS-SETTINGS-S5.md`) closed every earlier finding and accepted |
| +with one fix, applied in rev 7 (§13). |
| ## 1. Why |
| @@ -215,7 +217,7 @@ build its box holds as failed unless it has already served that build. |
| 1. **`pin` set:** install the pinned build, if §4.1 authorizes it. Bake |
| doesn't apply, because a person chose it. |
| -2. **An `install_request` R above the completed watermark.** The |
| +2. **A resolved `install_request` R above the completed watermark.** The |
| watermark is the **highest completed id known from either source**: the |
| box's own record (`/opt/carlos/consumed/<host>`) or the build state |
| object. It is never one source in place of the other, so an older local |
| @@ -225,7 +227,8 @@ build its box holds as failed unless it has already served that build. |
| - **If R has already been resolved to a build T** (locally or in the |
| build state), install T. That covers a pass that was interrupted, and |
| a request picked up on another box. |
| - - **Otherwise**, resolve R now: take the channel head that this box's |
| + - **Resolution** happens at the start of every pass, before the rules, |
| + for any pending R that has no T yet: take the channel head that this box's |
| own bake check says is eligible, or the current head if the request |
| asked for `FastTrack`. Write {R, T} to the root-owned file |
| `/opt/carlos/consumed/<host>` before staging. The agent mirrors it |
| @@ -240,7 +243,11 @@ build its box holds as failed unless it has already served that build. |
| new request. |
| - `manual` and `major` are ignored for this one move. |
| - **If the head is still baking**, R stays unresolved and the reason is |
| - reported. |
| + reported. **An unresolved R doesn't block anything else**: rule 2 |
| + applies only once R has a T, so until then the next rule decides. A |
| + held instance on a fresh box therefore still recovers onto |
| + `last_executed` (rule 3), and R is resolved later, when the head is |
| + eligible on this box. |
| 3. **`manual`, or `major = N` with a head whose semver tag is absent or has |
| another major:** the instance is **held**, and its target is: |
| - the build state's **`last_executed`**, if there is one. If the build |
| @@ -386,6 +393,9 @@ build whatever these settings say. |
| - **Completion:** a request completes at the pre-exec write, not at |
| install. A held sleeper with a pending request wakes onto T, not |
| `last_executed`. A failed T completes the request as failed. |
| +- **Unresolved request during recovery:** a held instance with an |
| + unresolved request, whose head is still baking, moves to a fresh box. It |
| + recovers onto `last_executed`, and the request resolves after the bake. |
| - **Watermark merge:** a box the instance returns to, with an older local |
| completion, doesn't replay a request the build state shows as |
| completed. |
| @@ -436,3 +446,10 @@ build whatever these settings say. |
| | S3-1 (partial), 1 | Backfill signs whatever is in the bucket now | §4.1: no automatic backfill. Older builds are recovered from a staged copy, refused elsewhere, or authorized explicitly by an app member (`carlos releases authorize`) | |
| | S2-4 (partial), 2 | An older local marker overrides a newer durable completion | §4.3 rule 2: the watermark is the highest id from either source; completed requests never re-enter | |
| | 3 | Completing at install loses the update for held instances | §4.2: completion is the pre-exec write; §4.3: a pending T outranks the held target in root and at wake; a failed T completes as failed | |
| + |
| +## 13. Astra S5 (rev 6): accept with fixes |
| + |
| +Every earlier finding is closed. The one fix: an **unresolved** request |
| +blocked recovery while the head was baking. Rev 7 makes rule 2 apply only |
| +to a **resolved** request, so recovery proceeds and the request resolves |
| +later (§4.3 rule 2). |
diff --git a/docs/superpowers/specs/CODEX-FINDINGS-SETTINGS-S5.md b/docs/superpowers/specs/CODEX-FINDINGS-SETTINGS-S5.md| new file mode 100644 |
| index 000000000..ccff9897e |
| --- /dev/null |
| +++ b/docs/superpowers/specs/CODEX-FINDINGS-SETTINGS-S5.md |
| @@ -0,0 +1,17 @@ |
| +ACCEPT WITH FIXES |
| + |
| +S4-1: **CLOSED** — Automatic backfill is removed; legacy authorization requires explicit publisher confirmation. |
| +S4-2: **CLOSED** — Completion uses the maximum local and durable watermark. |
| +S4-3: **CLOSED** — Requests remain pending until pre-exec, and their resolved target outranks held recovery. |
| +Carried S3-1 / S2-1: **CLOSED** — Added installs require digest-bound authorization, with the explicit legacy exceptions. |
| +Carried S2-4: **CLOSED** — Lost, unexecuted resolutions may be reconsidered; completed requests cannot replay through an older local watermark. |
| + |
| +1. **HIGH — §4.3 rules 2–3: An unresolved update request blocks recovery of an otherwise recoverable instance.** |
| + |
| + **Code:** [internal/provision/reconcile.go:394](/home/paulca/amadan.net/carlos/platform-design-zero-downtime-deploys/internal/provision/reconcile.go:394) creates destination routes without an installed build. [internal/release/bake.go:364](/home/paulca/amadan.net/carlos/platform-design-zero-downtime-deploys/internal/release/bake.go:364) calculates eligibility from the destination box’s observation time. [internal/release/materialize.go:47](/home/paulca/amadan.net/carlos/platform-design-zero-downtime-deploys/internal/release/materialize.go:47) routes cold materialization through adoption and fails when no executable is installed. |
| + |
| + **Failure:** Manual instance H has durable `last_executed=A`, with A available and its promotion record valid. The owner requests an update while head B is still baking, leaving R unresolved. H then moves to a fresh box or loses its existing box. |
| + |
| + Under the specified “first rule that applies decides” ordering, rule 2 matches R and waits for B’s bake. Rule 3 never selects A. The wake check has no resolved T, so its recovery target is A; however, A is not staged locally, and every relayed root pass again stops at rule 2. H remains unavailable throughout B’s bake window despite having a valid recovery build. Repeated head changes can prolong the outage. |
| + |
| + **Smallest fix:** While R remains unresolved, retain it as pending but allow rule 3 to recover and run `last_executed`. Only a **resolved** T should override that recovery target. Keep B’s bake check unchanged, and add a move/recovery test with an unresolved request. |
| \ No newline at end of file |