Tighten ruling 3: authorization is never cached, at any scope
An earlier wording said "cached no longer than a session", which conflated per-item authorization with team standing. The roster is the one copy an instance holds; S2 answers are never cached. Adds the browser-copy leak entry, pins the egress recipient as the app's choice and never the team's, and records that memoria decisions 13 and 14 stand. Raised by the memoria session.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JLq4NnZuUNpykg5nnkWdQg
1 file changed,
+25
−3
docs/superpowers/specs/2026-09-05-suite-sharing-doctrine.md+25 −3
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 6adf872..00cfc03 100644 |
| --- a/docs/superpowers/specs/2026-09-05-suite-sharing-doctrine.md |
| +++ b/docs/superpowers/specs/2026-09-05-suite-sharing-doctrine.md |
| @@ -47,7 +47,7 @@ happened to fall while every CARLOS app was one process. |
| |---|---|---|---| |
| | 1 | **The team is the boundary. An instance is a process.** A team's instances across every app are one trust domain at `<team>.<app>.oficina.works`. | One boundary per process, which left memoria holding person-initiated snapshots and made the home's activity feed a federation question | Paul, 2026-09-03 (memoria decision 1); reaffirmed suite-wide 2026-09-05 | |
| | 2 | **Sharing is membership across teams, unchanged.** No data crosses a team label. A guest is a scoped member of the granting team. A link share moves nothing. | Any federation story; a "copy to my team" verb | Pegamento §3, applied | |
| -| 3 | **Custody and authorization are two rules.** Custody bounds what a sibling may *hold*: team-visible items only. Authorization is what a *person* may read: asked of the app of record, per person, per query, cached no longer than a session. A copy is never an authority. | One ACL copied between apps; "is a member" as the only question | Paul, 2026-09-03 (memoria decision 3); generalised 2026-09-05 | |
| +| 3 | **Custody and authorization are two rules.** Custody bounds what a sibling may *hold*: team-visible items only. Authorization is what a *person* may read: asked of the app of record, per person, per query, **never cached**. Team standing is not authorization; it is the roster, a pulled copy refreshed by epoch (§5). A copy is never an authority. | One ACL copied between apps; "is a member" as the only question | Paul, 2026-09-03 (memoria decision 3); generalised 2026-09-05 | |
| | 4 | **The home owns identity and the team, including the membership roster. It never owns a per-item grant.** | Per-instance rosters as the record, reconciled by asking siblings; the home holding document-level grants | Paul, 2026-09-05 | |
| | 5 | **Two shapes, one switch.** A *lone* instance is its own identity authority and its own roster. A *suite* instance takes both from the home. Membership authority follows identity authority; an instance never has one without the other. | A suite instance half-trusting its local table; a lone instance dialling a home for a roster it does not need | Paul, 2026-08-31 (Docs §8.1, two shapes); extended to the roster 2026-09-05 | |
| | 6 | **Egress is one suite sentence.** Egress that is a property of the product is forbidden. Egress that is per-team opt-in, to one named recipient, disclosed, with a working floor when off, is admissible. | Two contradicting texts, Docs §11.2 and memoria §8.3 | Paul, 2026-09-03 (memoria decision 4); made suite-wide 2026-09-05 | |
| @@ -163,6 +163,15 @@ none collapsible into another: `VISIBLE`, `NOT_VISIBLE`, `STALE`, |
| `UNKNOWN`. Only `NOT_VISIBLE` may be silent. Everything else that is not |
| `VISIBLE` withholds loudly and names the app (memoria §4.1). |
| +**Never cached, at any scope.** Not per session, not per page, not for |
| +the life of a warm process. Caching authorization is what makes a stale |
| +answer a leak (memoria §6.5), and the usability pressure to cache within |
| +a session is exactly the pressure this sentence exists to resist. The one |
| +copy an instance may hold is the roster (§5.3), and the roster answers |
| +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. |
| + |
| **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 |
| @@ -272,7 +281,7 @@ spec, which owns them, and are cited here for completeness of direction. |
| | 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. | |
| | 8 roster | instance → home | consumer token | which instance asks | §5.3. Scoped to the caller's team. | |
| | 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 across requests. | |
| +| 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). | |
| | S3 content | sibling → sibling | consumer token | same-team sibling | `(id, revision)`, **no person**. Team-visible only. 404, never 403. | |
| | S6 aggregate | sibling → Sheets | consumer token + named asker | as S2 | Enumerated function over cell refs, never expression text. Cap 64. | |
| | S8 write-as-self | sibling → sibling | consumer token | same-team sibling | §7.2. Into the caller's designated area only; only items the caller authored. | |
| @@ -387,6 +396,11 @@ One sentence, binding on every app: |
| > paragraph and on every artefact it produced, with a working floor when |
| > it is off, is admissible.** |
| +**Which recipient is the app's decision, never the team's.** A team |
| +chooses whether, and never whom: a team-configurable endpoint would let an |
| +owner reinstate a proxy while every answer still printed "one named |
| +provider" (memoria decision 13, Paul, 2026-09-05). |
| + |
| Docs §11.2's rejection of conversion APIs stands under the first clause: |
| the egress was unavoidable and unnamed. memoria §8.3's model provider |
| stands under the second, and the comparison is like with like: memoria's |
| @@ -421,6 +435,12 @@ apps are a suite, and every one is a consequence of ruling 1 or 4. |
| 6. **The hand-off puts a document body in a POST** across two origins of |
| one site, in the person's browser. It is the person's own content |
| moving under the person's own session, and the receiver caps it. |
| +7. **A rendered page is a copy in a browser, and revocation never reaches |
| + it.** True of every surface in the suite: a Docs page, a memoria |
| + result, the home's activity list. Live authorization bounds what the |
| + *next* request shows; nothing bounds what the last one already did. |
| + Stated so nobody argues a surface is safe because it re-checks |
| + (memoria §3.2). |
| --- |
| @@ -641,7 +661,9 @@ person's own hand-off, and S8 as memoria itself into its designated |
| area."* |
| §8.3: replace the qualification of Docs §11.2 with a citation of suite |
| -§8. |
| +§8. Decisions 13 and 14 stand unchanged: §8 adopts 13's "the recipient |
| +is the app's, never the team's", and ruling 3's "never cached" is the |
| +invariant 14 rests on. |
| §13: close the blocker *"the source apps must adopt decision 1"* against |
| A.1 to A.3 when they land. |