oficina / suite Public

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
Paul Campbell pushed by paul@keymail.dev 6a239501033eb9c2acf12ba731159cfadba2abb9 parent 609394e
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.