spec: reconcile with what was built
Four amendments from implementation: the mirror refusal ("inherit" at app
scope) and the audit wording; the re-add consequence of set-membership
digests and the two tests it moved; healthByHost's extraction and the
upstream precedence; and the fleet caveat printing every call.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015gyhxeoXGBW4WCFRnMqBue
1 file changed,
+51
−7
docs/superpowers/specs/2026-08-16-route-scoped-config-delivery-design.md+51 −7
diff --git a/docs/superpowers/specs/2026-08-16-route-scoped-config-delivery-design.md b/docs/superpowers/specs/2026-08-16-route-scoped-config-delivery-design.md| index c8c4e92bb..67c44164e 100644 |
| --- a/docs/superpowers/specs/2026-08-16-route-scoped-config-delivery-design.md |
| +++ b/docs/superpowers/specs/2026-08-16-route-scoped-config-delivery-design.md |
| @@ -136,7 +136,16 @@ moves with it (`internal/console/apiconfig.go`): |
| ("exec_delivery is app-level only"). It accepts `"file"` and a new |
| `"inherit"` at environment scope, and refuses `"off"` there by name — |
| the same refusal the CLI gives, worded once on the server so the |
| - operator reads the server's sentence rather than a paraphrase. |
| + operator reads the server's sentence rather than a paraphrase. The |
| + **mirror refusal** is enforced too: `"inherit"` with no environment is a |
| + 400, because the default bundle has nothing above it to inherit from and |
| + silently treating the word as `"off"` would be the same class of quiet |
| + lie in the other direction. |
| + |
| +The audit row records the caller's **own word** (`file` / `off` / |
| +`inherit`) and the bundle it applied to. Both clearing words store `""`, |
| +so a row rebuilt from the stored value would lose which scope was acted |
| +on. |
| The two-CAS-writes shape of an apply-plus-exec_delivery request is |
| unchanged; the second write becomes `SetExecDeliveryAt` against the |
| @@ -251,6 +260,28 @@ spurious digest movement costs one pass that writes nothing — `SyncExec` |
| and `Sync` both compare rendered bytes and act only on a real change, so it |
| can never cost a restart. |
| +### 2.4 A consequence, recorded during implementation |
| + |
| +Set membership carrying scope changes means a route **leaving and |
| +re-entering** the registry now moves the digest, so the tick that re-adds |
| +it is no longer silent. Two existing tests asserted that silence |
| +(`TestGenPollPassDropsVanishedApps`, `TestGenPollPassPrunesUnitAppsToo`), |
| +and their assertion moved to the **settled tick after** the re-add. |
| + |
| +The ruling those tests encode is unchanged and still pinned directly: a |
| +vanished app's etag cache entry is dropped, so a re-add is a first sight |
| +rather than a comparison against a remembered etag. What is no longer |
| +separately observable from those tests is the etag half's *silence*, since |
| +the shape movement covers it either way. Firing there is correct on its own |
| +terms — a route entering or leaving scope changes what belongs on disk, a |
| +file to write or one to reap — and the pass it triggers resolves each case |
| +properly rather than acting on the digest. |
| + |
| +`genPollPass`'s signature took a `*genCache` (etags plus the two digests |
| +plus a `shapeSeen` bool) rather than growing a second parameter. The bool is |
| +not redundant with an empty digest: a box with no config-carrying routes has |
| +a real, stable digest of the empty set. |
| + |
| ## 3. `carlos routes` (#64) |
| ### 3.1 The defect |
| @@ -281,11 +312,21 @@ boundary the reconciler itself uses, and the account fallback is |
| `release.RouteAccount`'s, the same resolution every other read of a route's |
| account performs. |
| -Response rows carry: `host`, `kind`, `upstream` (addr or socket, whichever |
| -the row has), `app`, `channel`, `environment`, `status`, `health`, |
| -`hibernate`, `unit`, `backing`, `provisioned`. `health` is `healthWord`, |
| -computed the same way the Running tab and `carlos instances list` compute |
| -it, so the three cannot disagree. |
| +Response rows carry: `host`, `kind`, `upstream` (socket, then the |
| +multi-upstream set, then the legacy addr, then `tunnel:<name>`), `app`, |
| +`channel`, `environment`, `status`, `health`, `hibernate`, `unit`, |
| +`backing`, `provisioned`. Sorted by host, because registry order is not |
| +stable across passes. |
| + |
| +`health` is `healthWord`, computed the same way the Running tab and |
| +`carlos instances list` compute it, so the three cannot disagree. To make |
| +that structural rather than a promise, the metrics half of |
| +`instanceHealth` is **extracted** as `healthByHost(ctx, routeFor)` and both |
| +doors call it. The tenant scoping stays in each caller, beside the reason |
| +that caller's rows are the caller's to see; `healthByHost` does no check of |
| +its own and carries a comment saying it must never be handed an unfiltered |
| +registry listing (`OwnerAppMetrics`' allowlist is exactly the hosts and |
| +units it is given). |
| A console with no `Registry` configured cannot know any route's state; it |
| answers with an explicit "this console serves no registry" rather than an |
| @@ -321,7 +362,10 @@ answering the same question is not built. |
| The console reads *its own box's* registry, so in a multi-box fleet the |
| other boxes' routes do not appear. This is the same blind spot |
| `instanceHealth` has today. `internal/fleetapi` is the eventual fix and is |
| -unmerged (#145). The API doc comment and the CLI's own help say so. |
| +unmerged (#145). The API doc comment says so, and the CLI prints the |
| +caveat as a line **on every call**, not only when the list is empty — a |
| +short answer on a fleet is not a complete one, and the reader has no way to |
| +tell from the rows themselves. |
| ## 4. Testing |