Merge branch 'worktree-activity-reconcile' into main
1 file changed,
+51
−8
docs/superpowers/specs/2026-09-05-suite-sharing-doctrine.md+51 −8
diff --git a/docs/superpowers/specs/2026-09-05-suite-sharing-doctrine.md b/docs/superpowers/specs/2026-09-05-suite-sharing-doctrine.md| index 304dd32..57b9d81 100644 |
| --- a/docs/superpowers/specs/2026-09-05-suite-sharing-doctrine.md |
| +++ b/docs/superpowers/specs/2026-09-05-suite-sharing-doctrine.md |
| @@ -45,11 +45,15 @@ new findings. All three reports are beside this file, unedited: |
| **Openly not answered, and deliberately left for iteration** (Paul, |
| 2026-09-05): call 8's wire encoding and completion marker; result |
| -precedence when a stale roster and a non-custodial item collide; the |
| -activity path's cached authorization after revocation; pinning sibling |
| -baselines, since four memoria citations resolve only on an unmerged |
| -worktree and A.1's ruling number may collide with an existing home ruling |
| -22; and most of the fixtures the gates now describe. These are recorded |
| +precedence when a stale roster and a non-custodial item collide; pinning |
| +sibling baselines, since four memoria citations resolve only on an unmerged |
| +worktree; and most of the fixtures the gates now describe. |
| + |
| +Two that were on this list are off it. **The activity path's cached |
| +authorization** is answered: §4.3 now carves out call 7 explicitly and |
| +requires a hard maximum age, which the activity-pull session had already |
| +priced before being asked. **A.1's ruling number** is resolved: home ruling |
| +22 was indeed taken and the amendment landed as 23. These are recorded |
| here rather than in a passing sentence so the next reader can see the |
| edge of the work. |
| @@ -309,6 +313,29 @@ team standing, not "may this person read this item". An earlier draft of |
| ruling 3 said "cached no longer than a session", which conflated the two; |
| memoria's session caught it. |
| +**One carve-out, and it is call 7.** The rule above governs an authorization |
| +*answer*: an S2 `VISIBLE` held past its request is a standing capability, and |
| +it grows — the consumer re-uses it against items and people the app of |
| +record never spoke about. The home's activity cache is not that. It holds |
| +*rendered output* for one person, the home holds no grants, and it can only |
| +ever re-serve the same rows to the same person. |
| + |
| +That is a real distinction and it is not a free one. Inside the cache's life |
| +a person removed from a team still sees titles, and calling that "not cached |
| +authorization" would be word-play. So it is priced rather than denied: the |
| +activity cache carries **two ages**, a short refresh TTL saying when to try |
| +again, and a **hard maximum age after which rows are discarded regardless of |
| +the pull's outcome, failure included**. The hard maximum is the one that |
| +bounds the lie. Without it the tail is the hibernating fleet's wake schedule |
| +rather than a number anybody chose. |
| + |
| +This document states that the bound must exist and refuses to carry the |
| +number, exactly as §4.2 does for retention: the number is a property of the |
| +consumer, and the consumer states it. `design-activity-pull` §7 holds it. |
| +*(Reconciled with the activity-pull session, 2026-09-05, which found the |
| +contradiction between this section and §6's call-7 row and priced it before |
| +being asked.)* |
| + |
| **The app of record for an item is the app whose store it was created |
| in.** A copy never becomes an authority, and a derived artefact carries the |
| provenance of every source it was built from, transitively, so two hops |
| @@ -420,7 +447,17 @@ deserves its own words — **the instance decides nothing from the claim**. A |
| claim it cannot parse is ignored. A claim that differs from what it holds |
| buys exactly one call-8 refetch, and call 8's answer is what it enforces. |
| So a hostile claim costs one request and can neither admit nor exclude |
| -anybody. A.1 records that exception in the home spec, which owns the test; |
| +anybody. |
| + |
| +**The exception is scoped to the call-8 path and nothing else.** An instance |
| +answering call 7 sees the same assertion and therefore the same claim, and it |
| +must not invalidate anything on it — that would be an instance making a |
| +decision from a home-supplied claim, which is precisely what home §5.4 |
| +forbids and what this exception is narrow enough to avoid. Refetching a |
| +roster is not a decision; withholding an activity row would be. *(Raised by |
| +the activity-pull session, 2026-09-05.)* |
| + |
| +A.1 records that exception in the home spec, which owns the test; |
| this document cites it rather than asserting the old test already covered |
| it. A person whose sign-in reveals a stale roster is admitted against the |
| fresh roster, never against the claim. *(GPT-6 review, finding 12.)* |
| @@ -592,7 +629,7 @@ spec, which owns them, and are cited here for completeness of direction. |
| | Call | Direction | Credential | Proves | Notes | |
| |---|---|---|---|---| |
| | 1–6 | person → home, instance → home | session; consumer token | — | Home §9. Unchanged. | |
| -| 7 activity | home → instance | **assertion** for the person, `aud` = instance | who the front door is for | `design-activity-pull` §4–5. The home renders and discards; RAM cache only. | |
| +| 7 activity | home → instance | **assertion** for the person, `aud` = instance | who the front door is for | `design-activity-pull` §4–5 owns the wire shape. The home renders and holds rendered output only, under two ages (§4.3). Not in the contract module yet — §12 step 2 is S1, S2 and S3. When it arrives, the fixtures **cite** that spec's gates 10–20 rather than restate them, or this contract rebuilds §2's drift one layer down. | |
| | 8 roster | instance → home | consumer token | which instance asks | §5.3. Scoped to the caller's team. Paged; the copy has a TTL. | |
| | S1 change feed | sibling → sibling | consumer token | same-team sibling | `(item_id, revision, gone, touched_at)`. No title, no content, no visibility flag. Per-consumer TTL; past it, full re-announce. | |
| | S2 authorization | sibling → sibling | consumer token **+ named asker** | same-team sibling, asking for a person | Four values, total decoder, cap 256 pairs. **Never cached**: a passage crosses no request boundary without an S2 `VISIBLE` in the request that carried it (memoria §8.4, gate 26). Asker is a typed subject (§3); the answer is intersected with custody (§4.3). | |
| @@ -916,7 +953,13 @@ apps are a suite, and every one is a consequence of ruling 1 or 4. |
| calls to instances as a matter of course, its outbound traffic stops |
| being a compromise signal (activity design §2.2(b)). Both were |
| disclosed there and belong here too. |
| -11. **A link capability is bearer-shaped** (ruling 15). Whoever holds the |
| +11. **A cacheable call-7 response is a cross-person leak** at the platform |
| + edge, not merely a slow one. The shared edge would serve one person's |
| + rows to the home's next pull for another, so a cacheable response is a |
| + *failed* pull rather than a fast one. `design-activity-pull` gate 16 |
| + tests it; recorded here because the failure is in the platform between |
| + two apps and neither app's own tests would see it. |
| +12. **A link capability is bearer-shaped** (ruling 15). Whoever holds the |
| URL reads or writes the item, the app cannot name them, and a |
| forwarded link is a grant nobody recorded. This is what "anyone with |
| the link" means and there is no version of it that is not this. |