oficina / home Public

Cite the suite doctrine instead of restating it, and stop proposing a landed amendment

Coordination with the oficina/suite sharing doctrine (revision 4), which
adopts this design as suite contract call 7. Five points settled with that
session, and two defects found on the way.

- §1.1, new: consumed suite rulings, per their gate 9. Rulings 1, 3, 4, 7,
  8, 11 and 15, cited and not restated, with what this spec gave back in
  the other direction so each borrowing reads in the direction it happened.
- §3 stops reproducing the replacement clause. Their Appendix A preamble
  is right that a second copy of a boundary drifts, and A.1 owns the text.
  This document keeps the argument and drops the wording.
- §3 also retires the future tense. It still said "on approval, seven
  places change" and listed them; the approval came and 2b68301 landed
  them. A to-do list in a document whose edits have landed is a trap.
- §5.1 re-derived. Ruling 23 gives the home a membership roster, so the
  argument from the home's ignorance is gone. The conclusion stands on the
  caller supplying no refs and the subject riding an audience-bound
  assertion — a better footing, since the old one depended on a property
  the home has since acquired.
- §5 and gate 21: the assertion will carry roster_epoch, and call 7 must
  decide nothing from it. Refetching a roster is not a decision;
  withholding an activity row is. The exception is scoped to call 8.
- §7 records the doctrine's one carve-out from "never cached", including
  what it costs, and closes the item their header listed as openly
  unanswered. It was not open; the two ages bound it.
- §4: the wire shape stays here, the contract module's fixtures will cite
  gates 11 to 21, and gate 16 is now a suite leak-list item because the
  failure lives in the platform between two apps.
- §12: the doctrine's revision 4 is cited under two different commits and
  this clone can confirm neither. Recorded as open rather than resolved by
  taking the newer number. Also repairs an orphaned fragment.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rk1LgZH74vJxLbQXraA6re
Paul Campbell pushed by paul@keymail.dev 2e732fa3b5e5c18868c6f42bac7abbd59f0be639 parent 2b68301
1 file changed, +220 −24
  • docs/superpowers/specs/2026-09-05-activity-pull-design.md +220 −24

CI passed — run details

Intent

Attributed to this commit: 48.2 g CO₂e (5450208 tokens). This is an attribution, not a measurement — usage counted from this commit's own marker forward to the next one on design-suite-citations, 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-activity-pull-design.md b/docs/superpowers/specs/2026-09-05-activity-pull-design.md
index aab6362..567e3ac 100644
--- a/docs/superpowers/specs/2026-09-05-activity-pull-design.md
+++ b/docs/superpowers/specs/2026-09-05-activity-pull-design.md
@@ -1,7 +1,10 @@
# 🤖 Activity on the front door — design
-**Status:** design, 2026-09-05, revised three times the same day.
+**Status:** design, 2026-09-05, revised three times the same day and once
+again to coordinate with the suite sharing doctrine (see below).
Drafted by the activity-stream session (`claude-opus-5`).
+**The coordination revision has not been reviewed** — the four passes below
+predate it.
**Review passes: three, all by Fable 5.1, all returning CHANGES NEEDED.**
The first found six blocking (a superseded §11, §7 contradicting itself on
@@ -24,6 +27,16 @@ and one claim in §5 was falsified by §5.3's own last line. That session
wrote enough of the resulting argument to no longer be independent, and it
says so itself. **A fresh reviewer is still required.**
+**Coordinated with the suite sharing doctrine, 2026-09-05.** `oficina/suite`,
+`docs/superpowers/specs/2026-09-05-suite-sharing-doctrine.md`, revision 4,
+adopts this design as **suite contract call 7** (its ruling 11). Five points
+were settled with that session and are reflected below: the doctrine's §4.3
+now carves out call 7 explicitly, the wire shape stays here rather than moving
+into the contract module, the `roster_epoch` invalidation exception is scoped
+away from call 7, §1.1 records what this spec consumes, and §3 stops
+restating a clause the doctrine's Appendix A.1 owns. What changed here, and
+what changed there, is listed at §1.1.
+
**Scope:** how the home's front door (§11 of the home design) shows a person
what has been happening in the team they are looking at, across that
team's apps that they personally have rows in, without the home
@@ -86,6 +99,53 @@ load-bearing test in the document.
---
+## 1.1 Consumed suite rulings
+
+The suite sharing doctrine's gate 9 requires a consuming spec to name the
+document, its commit **and its revision**, and to list the rulings it takes —
+and to *cite* rather than restate them, because a second copy of a boundary is
+the drift the doctrine exists to stop. This section is that citation. Nothing
+below is restated here in its own words.
+
+**Document:** `oficina/suite`, `docs/superpowers/specs/2026-09-05-suite-sharing-doctrine.md`,
+**revision 4**. **Commit:** see the caveat below.
+
+| Suite ruling | What this spec takes from it |
+|---|---|
+| 1 | The team is the boundary; a team's instances across apps are one trust domain. This is why ruling 6's per-team scoping is the correct shape and not a narrowing. |
+| 3 | Custody and authorization are two rules, and authorization is never cached. §7 is the one carve-out, and §4.3 of the doctrine now writes it. |
+| 4 | The home owns identity, the team and the membership roster, and never a per-item grant. §5.1's argument is re-derived against this. |
+| 7 | One contract, one module, fixtures as the interface, and a consuming spec cites rather than restates. This section exists because of it. |
+| 8 | Two credentials chosen by what the call proves. Call 7 carries an assertion because it must prove *which person the call is for*, which a consumer token cannot (§5). |
+| 11 | Activity is pulled at render and held nowhere at rest — this design, adopted as call 7. |
+| 15 | Three rungs and a read-only link. Only the `team` rung is team-visible. Relevant to §5.2's guests, and the reason call 7 never needs a visibility flag. |
+
+**What this spec gave back, recorded so the direction of each borrowing is
+legible:** the doctrine's §4.3 call-7 carve-out is this §7's two ages,
+reconciled with that session; the epoch scoping in its §5.3 was raised from
+here; and its leak-list item 11, a cacheable call-7 response as a
+*cross-person* leak at the platform edge, is gate 16, credited there. Its §6.3
+sentence — *"a reliably slow sibling is a suite health problem the consumer
+surfaces, never one it routes around by caching authorization"* — is ruling 5
+generalised, and is cited in §8 as the doctrine's wording of it.
+
+**Caveat on the commit, unresolved and deliberately not papered over.** Gate 9
+wants a commit. Two have been named for revision 4: the `suite-amendment`
+branch in this repository cites `210312f`, and the doctrine session reports the
+coordinated revision landed on `oficina/suite` main as `bf0eeb8`. The second is
+the one carrying the call-7 carve-out this spec depends on, so **`bf0eeb8` is
+what §1.1 cites**, and the discrepancy is §12's problem rather than something
+to resolve by picking the newer number quietly. This clone cannot confirm
+either: its `oficina/suite` remote-tracking ref is behind both, and the text
+read for this revision was the working tree of that repository's
+`doctrine-corrections` worktree, not a commit. **That is the same class of
+defect the doctrine session flagged against its own memoria citations** — a
+citation resolving only in an unmerged worktree — and it is recorded here
+rather than hidden because this spec has no standing to complain about theirs
+otherwise.
+
+---
+
## 2. Why pull, and what it inherits from a parked design
**This shape was proposed before, in the other direction, and parked.** On
@@ -334,26 +394,37 @@ the honest version: "it never learns what is in anything" now has to be read
as *never retains*. That reading belongs in §0's own gloss rather than left
for the two documents to disagree about.
-§2.2's first never-do changes, and only its verb:
-
-> ~~**Never renders content from an instance.** No previews, no thumbnails,
-> no cell values.~~
->
-> **Never *stores* content from an instance.** No previews at rest, no
-> thumbnails, no cell values, no titles in a database, a log, an error
-> string, a metric label or an on-disk cache. It **may render**, transiently
-> and per request, **and only to that person**, what an instance hands it for
-> the person it hands it for.
-
-**On approval, seven places change, listed so nobody hunts:** this design's
-status block and §12's first bullet; home spec §0's gloss and §2.2's verb
-with its "until then" note; §11's "No activity feed yet"; §13 step 10's
-"must be revised"; §15's open bullet; and `AGENTS.md`'s "The one rule", which
-quotes §0 verbatim, and its four-things list. The same edit lands in `AGENTS.md`'s "four things this service never does" —
-**on approval, not now.** That file is binding and says the spec wins where
-they disagree, but a binding file must not be rewritten to follow a spec
-whose review has not run (§12). Until then, `AGENTS.md` is correct as written
-and this document is a proposal against it.
+§2.2's first never-do changes, and only its verb: *never renders* becomes
+***never stores***.
+
+**The replacement text is not reproduced here, and that is deliberate.** It
+lives in the home spec's §2.2 and in the suite doctrine's Appendix A.1, which
+owns the amendment as part of home ruling 23. An earlier revision of this
+section quoted the new clause in full; the doctrine's Appendix A preamble
+names exactly that as the defect — *"a replacement that puts a second copy of
+a boundary into an app spec and then adds a citation beneath it has left the
+second copy to drift"* — and it is right. **What this document owns is the
+argument for the change, below. What it does not own is the wording.** Read
+the clause where it is binding.
+
+**Landed, not proposed.** An earlier revision said "on approval, seven places
+change" and listed them. The approval came, and `main` carries the amendment
+at `2b68301`: the home spec's §2.2 and §0 gloss, and `AGENTS.md`'s "the one
+rule" and its four-things list, all read *never stores* as of 2026-09-05. The
+future tense is retired rather than merely updated, because a list of places
+still to edit, in a document whose edits have landed, is a reader's trap.
+`AGENTS.md` is no longer a binding file this design contradicts; it agrees.
+
+**§2.2's other two never-dos also change, and neither is this design's to
+land** — noted so the count in this section's title is not read as the count
+of changes to §2.2. §2.2's *never federates*
+becomes *never crosses a team*, and *never holds an authorization model*
+becomes *never holds a per-item grant* with the roster held explicitly —
+suite rulings 1 and 4, arriving as home ruling 23 on branch
+`suite-amendment`, not as part of this work. It is named here only because
+§5.1 rests on the old wording and had to be re-derived against the new (see
+§5.1), and because a reader comparing §2.2 against this document should know
+which of its changes came from where.
**Why the amendment is small rather than a hole in the doctrine:**
@@ -439,6 +510,28 @@ bug on its most public page.
with no grant gets, and what a quiet team gets. It is **not** what a broken
team gets — see §7, which is the entire point of ruling 5.
+**This spec owns the wire shape, and the suite contract module does not
+duplicate it.** Suite ruling 7 puts every inter-app call in one Go module with
+golden fixtures as the interface, which raised the question of whether call 7
+moves there. Settled with the doctrine session on 2026-09-05: **it does not.**
+Call 7 is absent from the module's first step, which builds the
+sibling-to-sibling calls only, and the doctrine's contract table records that
+this section owns the shape. When call 7 is built, **the module's fixtures
+cite gates 11 to 21 rather than restate them** — the reason being the one
+suite §2 is about, that a restatement is a second copy free to drift, and a
+fixture that has drifted from the gate it was written for is worse than no
+fixture because it passes.
+
+Two consequences worth stating. **Gate 16's cacheable-response case is now a
+suite-level concern**, recorded as item 11 on the doctrine's leak list,
+because the failure lives in the platform edge *between* two apps and neither
+app's own tests would ever see it — the home's tests do not run the edge, and
+the instance's tests do not know a second person exists. And **an instance
+implementing call 7 is conforming to this document**, not to a module, until
+that module carries it; a spec is a poorer interface than a fixture, which is
+§12's problem and not a reason to move the shape somewhere it would be
+restated.
+
---
## 5. Authentication, and why it needs nothing new
@@ -494,6 +587,24 @@ and it is an answer precisely because the home does not participate in it.
activity assertion for `acme.sheets` is not valid at `bigco.sheets`, so no
shared store is needed — §5.3's reasoning carries over untouched.
+**The assertion will carry `roster_epoch`, and call 7 must decide nothing from
+it.** Home ruling 23 adds the epoch to §5.3's claims table as a narrow
+exception to §5.4's boundary test — narrow because the epoch is not a closed
+enumerated set and does not fail cosmetically, and admissible because the
+instance decides nothing from it: an unparseable claim is ignored, a differing
+claim buys exactly one call-8 refetch, and call 8's answer is what is
+enforced.
+
+Call 7 rides the same §5.3 assertion, so it **inherits the claim whether it
+wants it or not**. The rule for this path is therefore explicit: an instance
+answering call 7 must not withhold, include, filter or reorder a single row on
+the strength of the epoch. Refetching a roster is not a decision; withholding
+an activity row is. An instance that let the epoch change its answer would be
+an instance making a trust decision from a home-supplied claim, which is the
+exact thing §5.4 forbids and the reason the exception is written narrowly.
+The suite doctrine's §5.3 scopes the exception to the call-8 path and nothing
+else. **Gate 21.**
+
**Rate limiting is the instance's.** The home is one caller and a
well-behaved one, but an instance must not assume that; a compromised home is
in scope for every instance's own limits.
@@ -518,6 +629,25 @@ Call 7 is a narrower shape, and the narrowness is deliberate:
- **Errors carry no detail.** "Not for you" and "no such thing" are one
response, per §12's gate 4 pattern of 404 rather than 403.
+**Re-derived against home ruling 23, because the ground it stood on moved.**
+§2.2 used to say the home *never holds an authorization model*, and an earlier
+reading of this section leaned on that: the home cannot be an oracle about
+who may see what, because it knows nothing about who may see what. Ruling 23
+replaces that clause with *never holds a per-item grant*, and gives the home a
+membership roster — so the home now does hold a fact about standing, and an
+argument from the home's ignorance is no longer available.
+
+The conclusion survives, on the two grounds above and only those: **the caller
+supplies no refs, and the subject is not caller-chosen.** Neither mentions
+what the home knows. This is a better footing than the one it replaces, not a
+weaker one — the old argument depended on a property the home might later
+acquire, and then the home acquired it. What the roster does add is a separate
+disclosure, priced in the suite leak list's item 9 and in home §0's amended
+gate rather than here: publishing the home's database now identifies each
+team's owners. That is a fact about *team standing*, which the home is now
+the record for; it is still not a fact about any item, and call 7 asks about
+items.
+
**What a leaked home does yield**, said plainly: an attacker holding the
signing key can mint an assertion for any person ref and read that person's
recent titles from any instance they have a grant on. That is strictly less
@@ -677,6 +807,31 @@ hard maximum.** Past it, the rows go and the gap is rendered (§8), because
that the grant may be gone. Gate 20 drives the hard maximum, not the refresh
TTL — a test against the refresh TTL proves the easy half.
+**This section is the suite doctrine's one carve-out from "never cached", and
+the carve-out is worded the way it is because of what it costs.** Suite ruling
+3 holds that authorization is never cached at any scope — not per session, not
+per page, not for the life of a warm process. Revision 4 of that document
+reached the opposite verdict about this section in two different places, and
+the reconciliation, settled with the doctrine session on 2026-09-05, is in its
+§4.3: the rule governs an authorization **answer**, and an S2 `VISIBLE` held
+past its request is a standing capability that *grows*, because the holder
+re-uses it against items and people the app of record never spoke about. This
+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.
+
+**The distinction is real and it is not free, and both documents say so rather
+than resting on it.** Inside the cache's life a person removed from a team
+still sees titles; calling that "not cached authorization" would be word-play.
+That is what the hard maximum is for, and why the doctrine requires the bound
+to exist while refusing to carry the number — the number is a property of the
+consumer, and **this section is where the consumer states it**, which is why
+§12 lists it as one of the four that must be settled before this ships.
+
+**What this closes.** The doctrine listed *"the activity path's cached
+authorization after revocation"* among the things it openly had not answered.
+It is not open. It is bounded, the two ages are the bound, and the remaining
+work is a number rather than a rule.
+
---
## 8. What the front door renders, including when it cannot
@@ -701,6 +856,16 @@ empty list, not an error*. A feed that silently drops what it could not fetch
is the worst available shape, because it looks exactly like a slow week and
nobody files a bug against a slow week.
+**The suite doctrine generalises this to every inter-app pull**, and its
+wording is worth having here because it names the pressure that breaks the
+rule rather than just the rule: *"a reliably slow sibling is a suite health
+problem the consumer surfaces, never one it routes around by caching
+authorization"* (suite §6.3). The temptation ruling 5 exists to resist is not
+laziness, it is kindness — an honest gap looks like a defect, a silent
+omission looks like a clean page, and the second one ships. That sentence is
+this ruling read back from the doctrine, and §7's hard maximum is the same
+principle applied to time rather than to a missing instance.
+
So each instance that failed to answer gets a row of its own, in words:
- `acme · sheets — waking, refresh` (deadline missed; likely cold)
@@ -816,6 +981,15 @@ being no-store (ruling 22), so these start at 11.
because the pull failed. A test against the refresh TTL proves the easy
half; this is the half that bounds the lie (§7).
+21. **`roster_epoch` changes no call-7 answer** (§5). Run the same pull twice
+ against the same instance with the same grants, once with a matching
+ epoch claim, once with an epoch far ahead of what the instance holds, and
+ once with an unparseable one. All three responses must be byte-identical.
+ An instance that filters, withholds or reorders on the epoch fails —
+ that is a trust decision from a home-supplied claim, and §5.4 forbids it.
+ The exception the epoch rides on is scoped to call 8; this gate is what
+ keeps it there.
+
---
## 10. Sequencing, and what blocks it
@@ -880,7 +1054,10 @@ is **a gap**, rendered as one, and gate 12 covers that case.
- **The review pass.** AGENTS.md requires the highest-capability model
available, recorded by name. **Run — four passes by Fable 5.1, the fourth
approving.** The three that returned CHANGES NEEDED are summarised in the
- status block. What remains open below is design, not review.
+ status block. What remains open below is design, not review. **Reopened in
+ one respect:** all four passes predate the coordination revision, so §1.1,
+ §3's retirement of the future tense, §4's module paragraph, §5's epoch rule,
+ §5.1's re-derivation, §7's carve-out and gate 21 carry no review at all.
- ~~`AGENTS.md` is stale against the home spec on three counts.~~
**Closed 2026-09-05:** `main` carries all three retractions at `5a42f2d`,
and `AGENTS.md` there is current.
@@ -904,8 +1081,27 @@ is **a gap**, rendered as one, and gate 12 covers that case.
main says either way.
- **Two platform facts** the "in RAM" claim leans on and which were not
established: whether swap is configured, and whether core dumps are
- disabled. Both must be answered before this ships (§2.1).
- , not a guess in a spec.
+ disabled. Both must be answered before this ships (§2.1). Both are
+ questions for the platform, not a guess in a spec. *(An orphaned fragment
+ of this bullet, left by an earlier edit, is repaired here rather than
+ deleted, since deleting it would have silently dropped the clause.)*
+- **Which commit revision 4 of the suite doctrine is** (§1.1). Two are named:
+ `210312f`, cited by this repository's `suite-amendment` branch, and
+ `bf0eeb8`, reported by the doctrine session as the coordinated revision on
+ `oficina/suite` main. §1.1 cites `bf0eeb8` because it is the one carrying
+ the call-7 carve-out, and this clone can confirm neither — its
+ `oficina/suite` remote-tracking ref is behind both. **Gate 9 of the doctrine
+ is not satisfied until one of them is confirmed**, and a citation that
+ resolves only in an unmerged worktree is precisely the defect that session
+ is chasing in memoria. Checked and clear on the other side: this spec's only
+ memoria reference is a general one in §11 and does not touch memoria
+ decisions 13 or 14, its gate 26 or its §8.4, the four the doctrine found
+ resolving only on an unmerged worktree.
+- **When call 7 joins the suite contract module** (§4). It is deliberately
+ absent from the module's first step, so until it arrives an instance
+ implementing call 7 conforms to a document rather than to a fixture. That is
+ a worse interface, accepted for now because the alternative was restating
+ the shape in two places.
- **Front-door layout** is the other session's. This spec deliberately does
not draw it.
- **Whether `actor` is worth having at all.** It is the field most likely to