plan: pin task 7's manifest source — the sibling is best-effort, the hot path is not
Task 3's review found the trap pair: converge-on-version-change reading the sibling would turn a failed best-effort write into leave-alone on the very version being pointed live, and Set: m.Capabilities verbatim would collapse resolved-declares-nothing into did-not-resolve, making capability removal unreachable. ensureStaged returns the manifest it fetched; the sibling re-read is the drift pass's posture only.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 file changed,
+4
−0
docs/superpowers/plans/2026-08-03-app-capabilities.md+4 −0
diff --git a/docs/superpowers/plans/2026-08-03-app-capabilities.md b/docs/superpowers/plans/2026-08-03-app-capabilities.md| index bc2e42683..7dd336ba2 100644 |
| --- a/docs/superpowers/plans/2026-08-03-app-capabilities.md |
| +++ b/docs/superpowers/plans/2026-08-03-app-capabilities.md |
| @@ -738,6 +738,10 @@ Because every route in every existing `release` test is exec-backed (`Backing == |
| **What the reviewer should try to break:** the canary fast loop picking up a capability-bearing version every 2-3 seconds and **not** converging it — meaning a freshly promoted canary runs the new binary against the old unit shape until the hourly root pass catches up. Decide and document: that window is accepted (the drop-in is repaired within the hour, and the alternative is the sandbox denial above), or the fast loop must skip capability routes entirely. **Recommended: accept, and log one line from the fast loop when it repoints a capability route so the window is visible.** Also: a hibernating unit route reaching `adoptHost` with `restart == nil` — converge must still run (its drop-ins are unit text, not a wake), but nothing may start it. |
| +**Two pins (added post-Task-3-review, 2026-08-03) — the orchestrator's ruling, binding on this task:** |
| +1. **The version-change converge builds its `Plan` from the `*Manifest` that `ensureStaged` already has in hand — change `ensureStaged` to return `(*Manifest, error)` — never from a sibling re-read.** The sibling write is best-effort (Task 3's C1/I2 fixes); re-reading it on the hot path would turn a failed sibling write into `(nil, nil)` → leave-alone → the new version pointed live with the previous version's drop-ins, silently. The sibling re-read belongs to Task 8's drift pass only, where leave-alone is the correct posture. |
| +2. **"Resolved but declares nothing" must not collapse into "did not resolve".** `Manifest.Capabilities` is `*caps.Set`, nil for both a legacy manifest and a fetch that never happened. When a manifest RESOLVED and `m.Capabilities == nil`, the `Plan` must carry `&caps.Set{V: caps.Version}` (or a separate `Resolved` flag) so converge REMOVES stale drop-ins, per spec §4d's removal semantics. `Set: m.Capabilities` verbatim would mean a capability dropped from a manifest is never removed — the exact opposite of the design. |
| + |
| --- |
| ### Task 8: `RunAdopt` — the grant-drift pass, the three advisories, and the DBPath stamp |