oficina / suite Public

Merge the drafting session's ruling-3 tightening into the review branch

The drafting session kept working on worktree-sharing-doctrine while the
review ran, so the two had diverged: ruling 3 tightened from "cached no
longer than a session" to "never cached", a §4.3 paragraph saying so, an
egress clause on who picks the recipient, and leak item 7 on a rendered
page being a copy revocation never reaches.

One conflict, the S2 row of §6's table, which both sides edited. Resolved
by keeping both: their "never cached" wording, which is stronger and
carries memoria §8.4 and gate 26, plus the review's typed asker subject
and custody intersection.

Their leak item 7 and the GPT-6 review's finding 6 are the same
observation reached twice — authorization bounds rendering, not the copy
already made. Item 7 states it for a browser; finding 6 states it for a
consumer's store, and is still open in §14.

The status block now says which change was whose: the review changed no
ruling, and ruling 3's tightening is the drafting session's.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016XuAriz559moY27sTegqpt
Paul Campbell pushed by paul@keymail.dev 6be28153d9aab3078fb20f9a8b61f026743ff73d parent 1008231parent 6a23950
1 file changed, +27 −4
  • docs/superpowers/specs/2026-09-05-suite-sharing-doctrine.md +27 −4

Intent

Attributed to this commit: 50.8 g CO₂e (4285963 tokens). This is an attribution, not a measurement — usage counted from this commit's own marker forward to the next one on worktree-doctrine-review, never backward from it. A turn that produced more than one commit books all of its tokens to the last one, and a concurrent session on this branch can add to the same figure. Some of it was priced using the shared default rate, because no model-specific factor row exists yet for at least one model involved. The Carbon view has the repo total, which is never derived from this attribution.

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 73bcd3f..ce227c5 100644
--- a/docs/superpowers/specs/2026-09-05-suite-sharing-doctrine.md
+++ b/docs/superpowers/specs/2026-09-05-suite-sharing-doctrine.md
@@ -15,7 +15,8 @@ are accounted for (§6), the roster copy gains a maximum age (§5.3), and
gate 9 gains a measurable unit (§11). Three change a rule: S2's answer is
intersected with custody (§4.3), the S2 asker subject is typed on the wire
(§3), and the hand-off gains an origin check and a confirmation step
-(§7.1). No ruling in §1 changed.
+(§7.1). The review changed no ruling in §1; ruling 3's tightening to
+"never cached" is a separate change by the drafting session, merged in.
**Read by a third hand, 2026-09-05: GPT-6 via Codex, verdict *request
changes*.** It confirmed all three rule changes as answering real defects,
and confirmed the fix for the typed asker subject outright. It found
@@ -65,7 +66,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 |
@@ -191,6 +192,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
@@ -351,7 +361,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. 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 across requests. Asker is a typed subject (§3); the answer is intersected with custody (§4.3). |
+| 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). |
| 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. Typed subject and the custody intersection, as S2. |
| 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. |
@@ -511,6 +521,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
@@ -548,6 +563,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).
---
@@ -823,7 +844,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.