spec: addendum — what was built for #299 and where it diverged
Records the six decisions the implementation made against the design: the checking state, the absent failed state, folding cert stamps rather than building the index, deferring the warm-outcome records and the expiry sweep, the reader-not-owner ruling, and the roll order. Each names what it costs to be wrong, and the 'still owed' list names the one part of the issue's ask this change does not make safe: the list door is still not a poller's door.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 file changed,
+97
−0
docs/superpowers/specs/2026-08-27-managed-custom-domains-design.md+97 −0
CI passed — run details
diff --git a/docs/superpowers/specs/2026-08-27-managed-custom-domains-design.md b/docs/superpowers/specs/2026-08-27-managed-custom-domains-design.md| index b2b0f78f0..ecd86af12 100644 |
| --- a/docs/superpowers/specs/2026-08-27-managed-custom-domains-design.md |
| +++ b/docs/superpowers/specs/2026-08-27-managed-custom-domains-design.md |
| @@ -604,3 +604,100 @@ live false-negative). Stages 3 and 2 together are what make `live` mean what |
| issuer — confirm the first pass orders nothing for the existing estate. |
| - Confirm which box currently holds the housekeeper lease on flagship and |
| tito, to size 1b's live blast radius. |
| + |
| +--- |
| + |
| +## Addendum — what was built, and where it diverged (2026-08-27) |
| + |
| +The spec above was written against `main` at `da9a7ee`. By the time it was |
| +implemented `main` had moved to `4235e9f` and had independently built most of |
| +stage 1 (#287's alias publication, #303's per-box stamps, the |
| +`Reconcile`/`StampHealth` split). Stage 1 was therefore re-derived as a |
| +salvage — see `.superpowers/sdd/salvage-onto-main/progress.md` — and stages 2 |
| +and 3 were built against the newer base. The divergences below are decisions, |
| +not omissions, and each names what it would cost to be wrong. |
| + |
| +### The vocabulary gained `checking` and never gained `failed` |
| + |
| +`checking` (attached; no edge has reported yet) was split out of `unknown`. |
| +Both mean "no verdict", but they are opposite news: one is the ordinary first |
| +few minutes after an attach, the other is a fault on our side. Collapsed, |
| +every successful attach opened with the word "unknown" — which an e2e test |
| +caught by asserting on the freshly-attached row. |
| + |
| +`failed` is absent. It would mean "the platform tried and stopped", which |
| +requires §3e's per-host warm-outcome records — not built (below). The spec's |
| +own honesty test, the one that renamed `verifying`, applies to it directly: |
| +a state nothing can produce must not be advertised. A test now pins that the |
| +advertised set and the producible set are exactly equal, so `failed` returns |
| +with §3e and not before. |
| + |
| +### §2d's cert index (§3h) was NOT built; stamps are folded instead |
| + |
| +The spec argued for an issuer-written host→NotAfter index because reading |
| +`control/certs/` on the request path means a Get and an x509 parse of a |
| +key-bearing blob per host, and because a wildcard-covered domain has no |
| +per-host entry at all. |
| + |
| +Both objections stand — against reading the BUCKET. They do not apply to the |
| +path actually taken: the boxes already stamp cert expiry into the registry, |
| +`status.LoadBoxes` already carries those stamps to the console, and the |
| +console already folds them across boxes (the freshest-stamp fold). Stage 2 |
| +reads that fold. The wildcard case is covered because `hostCertExpiry` |
| +already checks the covering wildcard PEM — and the `<host>+rsa` gap the spec |
| +flagged was fixed at the source, so the box-stamped path and any future index |
| +cannot disagree. |
| + |
| +Cost if wrong: the fold is bounded by box staleness, so a deployment whose |
| +boxes all go stale reads `checking` rather than `live`. That is the correct |
| +failure direction, and §3h remains available if the fold proves too coarse. |
| + |
| +Not building it also avoided a new bucket object, a new writer-table row, a |
| +new `aclRead` allowlist entry and an update to |
| +`TestDoorNeverServesCertMaterial` — none of which buy anything the existing |
| +stamps do not already carry. |
| + |
| +### §3e and §3f are follow-ups, not part of this change |
| + |
| +§3e (per-host `control/cert-warm/<host>.json` CAS records) and §3f (the |
| +expiry-bounded cert sweep) are not built. The warm works without them: it |
| +orders, it renews, it backs off per host, and it logs. What is missing is |
| +durable observability of warm outcomes across a failover, and the pruning of |
| +certs for long-detached hosts. |
| + |
| +Consequence stated plainly: warm attempt counts live in process memory and |
| +reset when the issuer moves, and `control/certs/` entries for detached hosts |
| +linger until something else removes them. Neither is a correctness or safety |
| +problem — §3f's whole ruling was that the sweep must key on EXPIRY rather |
| +than detachment, so nothing is being deleted wrongly by not running it. |
| + |
| +### The shared reader is a READER, not the wire-shape owner |
| + |
| +`internal/edgesfile` reads the deployment-wide edges picture. It does not own |
| +the writer's struct: `internal/host` had developed that file substantially |
| +(wildcard parents, withheld-alias throttling, EIP-reuse-on-blank), and |
| +re-plumbing it onto a shared type would have fought a file under active |
| +development for no gain to #299. The two shapes are joined by a test instead, |
| +which also preserves the additive-wire property — the writer can add a field |
| +and roll before every reader understands it. |
| + |
| +### Rollout |
| + |
| +The spec required stage 3's fleet roll to precede stage 2's console roll, so |
| +that `issuing-cert` never names activity that cannot happen. Both ship in one |
| +branch, so the constraint applies to the ROLL and not the merge: **roll the |
| +fleet before the consoles.** Rolled the other way, every pointing-but-certless |
| +domain reports `issuing-cert` while nothing is issuing. |
| + |
| +### Still owed |
| + |
| +- §2e's poller contract: `GET .../domains` remains behind `instanceGate`'s |
| + shared 60/min budget and still costs one Get per claim in the bucket. A |
| + status poller must not be pointed at it until the #165 claims index or a |
| + per-host door lands. **This must be said on #299** — it is the one part of |
| + the issue's ask this change does not make safe. |
| +- `deploy.go`'s `domainsDoorMissing` 404-sniff could now be retired in favour |
| + of the capability block. Left in place: it still works, and retiring it is |
| + not free of risk against older consoles. |
| +- The console Settings page still renders the pre-state wording. The CLI and |
| + the deploy exit moved; the page has not. |