docs+cli: the re-review's three minors — name the daemon start, the whole fix, the stranded grants
The -backing unit stamp now warns at the terminal that the hourly adopt pass STARTS a daemon route (F4's guard belongs at the moment of typing, not only in §5); the missing-echo refusal names the full fix including the new-version re-ship; both §5 rollbacks note that grants stay in the caps column after an un-stamp and what the clearing order is.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 files changed,
+15
−3
apimode.go+5 −1cli.go+1 −0docs/superpowers/specs/2026-08-03-app-capabilities-design.md+9 −2
diff --git a/apimode.go b/apimode.go| index e116fea96..048b15b25 100644 |
| --- a/apimode.go |
| +++ b/apimode.go |
| @@ -477,7 +477,11 @@ func checkCapabilitiesEcho(sent *caps.Set, body []byte) error { |
| Capabilities json.RawMessage `json:"capabilities"` |
| } |
| if err := json.Unmarshal(body, &echo); err != nil || len(echo.Capabilities) == 0 { |
| - return fmt.Errorf("ship: the console accepted this release but its response did not echo the capability block back: %s", updateFirst) |
| + // The version is already in the bucket WITHOUT its declaration |
| + // (immutable, so a same-version re-ship after the update meets the |
| + // 409) — name the whole fix here, not just the console half. |
| + return fmt.Errorf("ship: the console accepted this release but its response did not echo the capability block back — "+ |
| + "the stored manifest carries no capabilities and must not be promoted: %s, then ship again under a new version", updateFirst) |
| } |
| var got *caps.Set |
| if err := json.Unmarshal(echo.Capabilities, &got); err != nil || got == nil { |
diff --git a/cli.go b/cli.go| index cf9f6c5f5..58a595993 100644 |
| --- a/cli.go |
| +++ b/cli.go |
| @@ -1043,6 +1043,7 @@ func runRouteBacking(r *registry.Registry, rt registry.Route, backing string, dr |
| // column change sides together. |
| if backing == registry.BackingUnit { |
| fmt.Printf(" the next adopt pass converges this route's capability drop-ins and config sync writes /etc/carlos/apps/%s.env; the agent stops materializing /data/%s.config.env\n", rt.Host, rt.Host) |
| + fmt.Printf(" for a daemon route that pass also enables and STARTS carlos-app@%s.service — the adopt timer is hourly, so stamp this only once the data it will serve is in place\n", rt.Host) |
| } else { |
| fmt.Printf(" the next adopt pass stops converging this route's capabilities (any drop-ins already written are left exactly as they are, for an operator to remove) and config delivery returns to the agent's /data/%s.config.env\n", rt.Host) |
| } |
diff --git a/docs/superpowers/specs/2026-08-03-app-capabilities-design.md b/docs/superpowers/specs/2026-08-03-app-capabilities-design.md| index 5d0e14676..bdf66435a 100644 |
| --- a/docs/superpowers/specs/2026-08-03-app-capabilities-design.md |
| +++ b/docs/superpowers/specs/2026-08-03-app-capabilities-design.md |
| @@ -675,7 +675,12 @@ Rollback: stop and disable the templated unit, |
| `carlos route -host <host> -backing ""`, re-enable `console.service` (its |
| data is untouched at `/data/console`). Console writes made after cutover are |
| lost by rolling back — keep the window short, the same posture the keymail |
| -cutover plan takes about its fork window. |
| +cutover plan takes about its fork window. Note the grants: they stay in the |
| +caps column after the un-stamp, reading like live authority, and `-revoke` |
| +refuses non-unit routes — so to actually clear them the order is re-stamp |
| +`-backing unit` → `-revoke` each token → `-backing ""`. Harmless if left |
| +(nothing materializes off a non-unit route), but say so in the ops notes |
| +rather than let a later reader wonder who granted what. |
| ### 5c. keymail relay |
| @@ -743,7 +748,9 @@ Rollback: stop the templated unit, `carlos route -host keymail.dev -backing ""`, |
| re-enable `keymail-relay.service` — its data is still at `/opt/keymail/data` |
| and its own embedded litestream resumes from local files. Mail accepted |
| during the window exists only in the streamed lineage; short window, quiet |
| -hour, same caveat the cutover plan already carries. |
| +hour, same caveat the cutover plan already carries. The same stranded-grants |
| +note as §5b's rollback applies: `port:25`/`host-role` stay in the caps |
| +column and clearing them means re-stamp → revoke → un-stamp. |
| **Implementation note (2026-08-03): `-backing` and `-grant`/`-revoke` are |
| real, and this is their exact syntax.** Both §5b and §5c above reference |