carlos / platform Public

spec: settings rev 10 — §4.6 covers static sites (Astra S7 accept-with-fixes, fix applied)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 3a9658c634210129b04937edfe3446856ba7fb98 parent ca26f97
2 files changed, +33 −2
  • docs/superpowers/specs/2026-10-07-instance-update-settings-design.md +22 −2
  • docs/superpowers/specs/CODEX-FINDINGS-SETTINGS-S7.md +11 −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 3b403dd2f..e17196bed 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: rev 9, 2026-10-07. Rev 7 was accepted by Astra (S5: accept with fixes, the fix applied) and **approved by Paul**. Rev 8 adds §4.6, which closes #253 at Paul's request, and is in review for that section. Split out of the zero-downtime deploys
+Status: rev 10, 2026-10-07. §4.6 accepted by Astra (S7: accept with fixes, the fix applied). Rev 7 was accepted by Astra (S5: accept with fixes, the fix applied) and **approved by Paul**. Rev 8 adds §4.6, which closes #253 at Paul's request, and is in review for that section. 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.
@@ -323,6 +323,23 @@ that digest before staging. Staging's artifact sha check then binds the
bytes. On a mismatch, root refuses the build and reports it. This covers
ordinary head adoption, history-based adoption and rollbacks.
+**Static sites read pointers too.** Static routes don't go through root's
+adoption pass. The edge verifies the pointer itself, then extracts the site
+archive (`internal/edge/static.go`, `ensureExtracted`). That reader follows
+the same contract:
+- the pointer entry's digest is carried into extraction;
+- the manifest used to choose the archive must hash to that digest before
+ anything is downloaded;
+- the extracted-site cache is keyed by the verified manifest digest as
+ well as the version name, so a cached tree from different bytes is
+ never reused;
+- the per-box ratchet and `CARLOS_REQUIRE_POINTER_DIGEST` apply to the
+ edge's pointer reads as they do to root's.
+
+The ratchet's mark for the edge is kept in the edge's own state directory,
+because the sandboxed edge can't write `/opt`. It is a read-side
+protection on that box, as root's mark is on root's side.
+
**Entries without a digest.** A head without a digest is handled by the
ratchet and the flag below. A **history entry** without a digest is never
a rollback target once its channel has any digest. A rollback onto it is
@@ -497,7 +514,10 @@ build whatever these settings say.
- a rollback onto a history entry without a digest is refused until it
is resigned;
- writers emit digests only once the marker exists, and the console
- flags a pointer without digests written after the marker.
+ flags a pointer without digests written after the marker;
+ - a static site whose version name is reused, under a pointer with
+ digests, is refused before extraction, and a cached tree from other
+ bytes isn't served.
- **Evidence:** a pin of a pruned and reused version name, with a replayed
pointer, is refused, because it lacks a matching promotion record. A
build promoted before the feature is recovered from a staged copy, is
diff --git a/docs/superpowers/specs/CODEX-FINDINGS-SETTINGS-S7.md b/docs/superpowers/specs/CODEX-FINDINGS-SETTINGS-S7.md
new file mode 100644
index 000000000..2fcfe33e9
--- /dev/null
+++ b/docs/superpowers/specs/CODEX-FINDINGS-SETTINGS-S7.md
@@ -0,0 +1,11 @@
+ACCEPT WITH FIXES
+
+S6-1: **CLOSED** — Inherited missing digests remain empty; rollback requires explicit resigning.
+S6-2: **CLOSED** — `FastTrack` explicitly preserves signed digests.
+S6-3: **CLOSED** — Writer upgrades now precede digest emission, gated by the signed marker.
+
+1. **High — §4.6, “Root checks before staging”: static releases bypass the specified check.** Code: [internal/edge/static.go:217](/home/paulca/amadan.net/carlos/platform-design-zero-downtime-deploys/internal/edge/static.go:217), [internal/edge/static.go:259](/home/paulca/amadan.net/carlos/platform-design-zero-downtime-deploys/internal/edge/static.go:259), [internal/release/adopt.go:830](/home/paulca/amadan.net/carlos/platform-design-zero-downtime-deploys/internal/release/adopt.go:830).
+
+ Static routes do not enter root’s adoption pass. The edge verifies their pointer, passes only `cp.Version` to `ensureExtracted`, and checks the downloaded archive against the unsigned manifest. Replay a **digest-bearing** pointer for a pruned, re-shipped static version on a box without that version cached: the edge serves the replacement bytes without comparing the manifest to the signed digest. Neither the ratchet nor the flag stops this—the pointer has a digest.
+
+ **Smallest fix:** explicitly include static serving in §4.6’s enforcement contract. Carry the pointer digest into extraction, verify the exact manifest used to select the archive, and bind cache reuse to the verified identity. Apply the legacy-pointer ratchet and flag to this reader too.
\ No newline at end of file