oficina / home Public

Fold the approving review, and land the row that was reported fixed twice

Approved at the fourth pass. Nit 1 was the one that had to land: the
finding-1 row's right column still said "the ruling was reopened
deliberately — the first of the two paths", while its own left column
and §2.1 said the second. Within one row the design gave two answers
about the verdict's most important finding. It now says what is true:
§2.2's verb was reopened; §5.4 was not, because the cross-team want was
declined instead, which is the verdict's second path and where ruling 6
landed.

That is the second correction in this branch reported as applied and
found not to be. Every fix in this commit was grepped for — old text
absent, new text present — before it was written down.

Ruling 8 records Paul's call on the framed variant in §1, where a
reader of the settled-decisions table will actually see it. Browser
composition satisfies ruling 2 exactly as written — pull at render,
nothing at rest — so without its own row a future reader would
reasonably conclude it was never considered.

Two costs of that variant were understated and are now right. "No
home-side throttle possible" is too strong: the home authors the markup
and could withhold a frame, but having no rows of its own it can only
replace it with nothing, so the precise claim is no throttle that keeps
rows on the page. And every instance would have had to relax its
framing policy for the apex, since rastrillo sends frame-ancestors
'none' and X-Frame-Options: DENY by default — another cost pointing the
same way as the ruling.

The status block carried the new three-pass summary and the old
single-pass paragraph, which contradicted it. §3 now lists the seven
places that change on approval, so the landing commit is not a hunt.
And an integrated: false app cannot answer call 7 at all, because it
verifies no §5.3 assertion — the structural reason, where §11 had only
the missing spec.

Plus: the §5.3 quote marked as pre-retraction wording, "method" dropped
from the edge cache key, an instance rather than a team deleted in the
call-2 citation, "the seventh call" where §9 now has 4b, a duplicated
sentence in §3, the last "apps in one team" phrasing, and gap examples
that showed two teams on a page ruling 6 makes single-team.

Reviewed by Fable across four passes; the fourth approved.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev 6f7e6a995e348389a32c674e7428f8b16acafade parent 6aee20e
1 file changed, +31 −30
  • docs/superpowers/specs/2026-09-05-activity-pull-design.md +31 −30

CI passed — run details

Intent

Attributed to this commit: 25.5 g CO₂e (2173010 tokens). This is an attribution, not a measurement — usage counted from this commit's own marker forward to the next one on design-activity-pull, 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 1d0b460..aab6362 100644
--- a/docs/superpowers/specs/2026-09-05-activity-pull-design.md
+++ b/docs/superpowers/specs/2026-09-05-activity-pull-design.md
@@ -12,19 +12,8 @@ set stated two ways, one of which disclosed a guest's sibling apps; §2's
framing backwards after the rescope; §2.1 answering an alternative nobody
proposed). The third found that §2.1's credential argument was not the whole
story — browser-mediated composition needs no amendment at all — which Paul
-then ruled on. **This revision has not been re-reviewed.**
-**Review pass: run 2026-09-05 by Fable 5.1**, which returned CHANGES NEEDED
-with six blocking findings and eight required corrections. All are applied in
-this revision, and the revision has not yet been re-reviewed — so this is
-still not approved, but it is no longer unreviewed. The six were: the design
-was written against a superseded §11 and did not say whether the feed was
-cross-team; §7 contradicted itself on whether cached rows survive a failed
-pull, with the safety claim on the losing side; §10's 404 rule inverted
-ruling 5; the platform edge is a shared cache upstream of both ends and the
-cache-header sentence did not reach it; §2.1 understated the RAM cache as one
-person's when it is every recent person's; and two of the open numbers were
-load-bearing rather than tunable.
-
+then ruled on. **The fourth pass APPROVED**, and the nits it listed are
+applied in the commit that carries this line.
**Peer pass, and why it is not the review pass.** The home-spec-compliance
session (`claude-opus-5`) reviewed the first draft and found four things,
all applied: the "no new knowledge" defence was true and beside the point
@@ -83,14 +72,16 @@ What it costs is latency, honestly rendered. §6 and §7.
|---|---|---|---|
| 1 | **A real feed with titles, not a contentless heat map** | "4 changes in acme·sheets" with no titles; no feed at all | Paul, 2026-09-05 |
| 2 | **Pull at render. The home holds no activity at rest** | Push into the home (the parked shape); push-a-signal / pull-detail hybrid | Paul, 2026-09-05 |
+| 8 | **Server-side pull, and amend §2.2 — not browser-mediated composition** | An `<iframe>` per instance, which satisfies ruling 2 as written and would leave §2.2 unamended, at the cost of the ordered merge, the server deadline, and any rows at all for a viewer without a live session at that sibling — §2.1 | Paul, 2026-09-05 |
| 3 | **Reuse the §5.3 assertion for the pull. No new home→instance credential** | A dedicated service token per instance | This session, 2026-09-05 |
| 4 | **The last good pull is cached in RAM per person, never at rest** | No cache (every load pays cold wake); a cache on disk (stores content) | This session, 2026-09-05 |
| 5 | **A silent instance renders as a named gap, never as an absent row** | Rendering only what arrived | This session, 2026-09-05 |
| 6 | **Scoped to the selected team, following `?team=<sqid>`** — home spec ruling 18 | A cross-team merged list, which this design specified and argued for until 2026-09-05; the argument is retired in §2.1 rather than deleted | Paul, 2026-09-05 |
| 7 | **Participation is a registry flag, never inferred from a `404`** — §10 | Treating a 404 as "does not participate", which makes a broken or hostile instance vanish silently and inverts ruling 5 | This session, 2026-09-05 |
-Rulings 3, 4, 5 and 7 are this session's and were flagged as such. This design is not
-approved — see the status block. Ruling 5 is not a courtesy: §2 explains why it is the
+Rulings 3, 4, 5 and 7 are this session's and were flagged as such. Ruling 8
+is Paul's, taken after the third review found an alternative that would have
+needed no amendment at all (§2.1). Ruling 5 is not a courtesy: §2 explains why it is the
load-bearing test in the document.
---
@@ -130,7 +121,7 @@ The findings that bear on this design, and what happens to each:
| # | The finding | What happens to it here |
|---|---|---|
-| 1 | ~~**It answered a different question.**~~ **Overtaken by ruling 6: per team is now the shape.** Per-team aggregation gives a person in three teams three lists, which the verdict called the wrong answer and Paul has since ruled the right one — the verdict's second path, not its first. The §5.4 argument below stands on its own and is what still matters. The cross-team list is forbidden at the only place that could hold it — §5.4, *"'Which documents you can see' passes (a) and fails (b), handing the home a document-level graph it has no business holding."* **Ruling 6 took the verdict's SECOND path** — three lists, per team — so what was reopened is §2.2's verb, not §5.4. The verdict's own resolution: *"either that ruling is reopened deliberately or the want is declined."* | **The ruling was reopened deliberately** (rulings 1 and 2, Paul, 2026-09-05) — the first of the two paths the verdict named, taken with the sentence quoted in front of us. §2.1 argues why a rendered page is not a held graph. |
+| 1 | ~~**It answered a different question.**~~ **Overtaken by ruling 6: per team is now the shape.** Per-team aggregation gives a person in three teams three lists, which the verdict called the wrong answer and Paul has since ruled the right one — the verdict's second path, not its first. The §5.4 argument below stands on its own and is what still matters. The cross-team list is forbidden at the only place that could hold it — §5.4, *"'Which documents you can see' passes (a) and fails (b), handing the home a document-level graph it has no business holding."* **Ruling 6 took the verdict's SECOND path** — three lists, per team — so what was reopened is §2.2's verb, not §5.4. The verdict's own resolution: *"either that ruling is reopened deliberately or the want is declined."* | **§2.2's verb was reopened deliberately** (rulings 1 and 2, Paul, 2026-09-05). **§5.4 was not** — the cross-team want was declined instead, which is the verdict's *second* path, and ruling 6 is where that landed. §2.1 argues why a rendered page is not a held graph. |
| 2 | **Titles are first-class sealed content, not cheap pointers.** Sheets §2.1 seals titles under its DEK; a mail "title" is a subject line — *"Re: your termination"*. Any indexer needs Sheets-grade custody. | **Survives, and is ruled through** (ruling 1). The instance decrypts under its own DEK and hands the title over for one render. The home stores nothing, so it never acquires the custody obligation — §3. **Also kills the stale-title leak** the verdict names ("Project Blue" still listed under its old name), which no cached copy can avoid and a pull cannot produce. |
| 3 | **`visible_to` is not a list, it is the grants join** — team rows plus individual grants minus removals. Either the app fans out one membership change to N item pushes, or the indexer re-implements the rule and drifts, failing as *a plausible empty list, not an error*. | **Dissolved by the inversion.** Nobody posts an ACL because nobody posts. The instance evaluates its own join at read time for the person in the assertion. There is no second evaluator and no copy to drift from. |
| 4 | **"Seconds" for a revoke is a median with an unbounded tail.** The carrier is *Sheets'* outbox, and Sheets §9.1.1 says *"for a quiet team a failed upsert may wait weeks for an organic wake, which is acceptable precisely because the directory is a hint"* — an argument that does not transfer to a rendered list. | **Dissolved by the inversion.** Revoke is instant because the answer is computed after it, every time. The only residue is the RAM cache TTL — §7. |
@@ -223,7 +214,11 @@ alternative that needs no amendment would be the wrong way to get there.
without a live session at that sibling gets a sign-in prompt where rows
should be, which is most guests and every first visit; a hostile instance
draws pixels on the suite's most public page; and §6's guest-wake billing
- problem survives with **no home-side throttle possible**, because the home
+ problem survives with **no home-side throttle that keeps rows on the page**
+ — the home authors the markup so it could withhold a frame, but having no
+ rows of its own it can only replace it with nothing. **And every instance
+ would have to relax its framing policy for the apex**: rastrillo sends
+ `frame-ancestors 'none'` and `X-Frame-Options: DENY` by default. The home
is not in the request path. Recorded as considered and costed, not as
overlooked.
- **Instance → instance under the caller's own consumer credential**, passing
@@ -306,7 +301,7 @@ than by compromise. **Mitigation is thin and named as thin:** the RAM cache
(§7) collapses repeat renders within its TTL, so the emission is per-TTL
rather than per-load. Nothing removes it.
-**(b) It spends a detection property.** home spec §9's calls, call 4b included are uniformly
+**(b) It spends a detection property.** home spec §9's calls, call 4b included, are uniformly
person→home or instance→home; **zero run outward**. That uniformity is a
security property rather than an accident of drafting: today a home making
content-bearing requests to instances is anomalous *by construction*, so such
@@ -337,8 +332,7 @@ publish the database and you still learn only counts, team names,
memberships and §3.2's list. **Its prose form does move**, and saying so is
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. Publish the database and
-you still learn only counts, team names, memberships, and §3.2's list.
+for the two documents to disagree about.
§2.2's first never-do changes, and only its verb:
@@ -351,7 +345,11 @@ you still learn only counts, team names, memberships, and §3.2's list.
> and per request, **and only to that person**, what an instance hands it for
> the person it hands it for.
-The same edit lands in `AGENTS.md`'s "four things this service never does" —
+**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
@@ -390,7 +388,7 @@ response body. §11.
## 4. Call 7 — the activity pull
-The seventh call in §9's contract, and **the first that runs home →
+The next call in §9's contract, and **the first that runs home →
instance**. Every existing call is instance → home or person → home. That
direction change is the main thing an implementer needs to hold in mind.
@@ -470,7 +468,8 @@ through the browser.
"every property §5.3 relies on is a property of the token, not of how it
travelled." **That is false, and §5.3's own last line is the counter-example:**
"Delivery is an auto-POST, not a query string, so assertions stay out of edge
-logs, browser history and referrers" — a property of travel, not of the
+logs, browser history and referrers" (the pre-2026-09-05 wording; main now
+reads "browser history and referrers") — a property of travel, not of the
token. The conclusion survives on the specific argument rather than the
general one: an `Authorization` header achieves the same two things an
auto-POST still claims — its third leg, edge logs, having been removed: §5.3's "edge logs" reason was
@@ -481,7 +480,7 @@ assertion moving into the query string** — the failure §5.3 was guarding
against, which a header avoids for the same reason a POST body does.
**Why not a service token.** A per-instance home→instance bearer would be a
-seventh credential to mint, store, rotate, revoke and leak, and it would
+further credential to mint, store, rotate, revoke and leak, and it would
prove only that the caller is the home — not *which person the call is for*.
The assertion proves both in one artifact the suite already verifies. Ruling
3.
@@ -638,7 +637,7 @@ in process memory, with a short TTL.
permits rendering, not storing, and a disk cache is storing.
- **The platform edge is a shared cache upstream of both ends, and it must
be closed on both.** It caches any 2xx GET whose response carries a
- positive `max-age`, keyed on host, method, path and query, shared across
+ positive `max-age`, keyed on host, path and query, shared across
every visitor of a host — and it does **not** refuse on a request
`Cookie`. So: the front door sends `Cache-Control: no-store` (the home
spec's gate 10), **and call 7's contract requires instances to send
@@ -705,7 +704,7 @@ nobody files a bug against a slow week.
So each instance that failed to answer gets a row of its own, in words:
- `acme · sheets — waking, refresh` (deadline missed; likely cold)
-- `bigco · docs — unavailable` (error, refused, or unparseable)
+- `acme · calendar — unavailable` (error, refused, or unparseable)
- `acme · sheets — showing what we had at 09:14` (cache served, live pull failed)
Each with a refresh link, which is a plain `GET` and works with scripts off.
@@ -736,7 +735,7 @@ front door is *for*.
**One consequence to expect rather than fix.** The directory is a hint with a
drain lag (§6), so the home will sometimes call an instance the person was
-removed from weeks ago, or one whose team was deleted (home spec §9's call 2, whose second
+removed from weeks ago, or one whose instance was deleted (home spec §9's call 2, second
event). §5.5 makes that safe — the answer is an empty list, or a 404 — but
ruling 5 then renders a named gap for a team they have left, or an
"unavailable" for a host that no longer exists.
@@ -871,7 +870,7 @@ is **a gap**, rendered as one, and gate 12 covers that case.
| Search across the feed | Never at the home. `memoria` is where cross-corpus search lives, and it has its own custody story. |
| Filtering by app, team, person, or date | Someone asks. It is rendering, not custody, so it is cheap — but the front door is a front door. |
| Notifications, unread counts, marking read | Any of these needs per-person read state at the home, which is new custody. Argue it separately. |
-| Calendar and mail as sources | `oficina/calendar` has a repo and no spec; `oficina/mail` has neither. |
+| Calendar and mail as sources | `oficina/calendar` has a repo and no spec; `oficina/mail` has neither. And the structural reason, not just the missing spec: an `integrated: false` app verifies no §5.3 assertion (home §11, §14), so it cannot answer call 7 at all. |
| An operator view of anyone's feed | Never (§2.2, §0). |
---
@@ -879,7 +878,9 @@ is **a gap**, rendered as one, and gate 12 covers that case.
## 12. Open
- **The review pass.** AGENTS.md requires the highest-capability model
- available, recorded by name. Not run. This spec is not approved.
+ 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.
- ~~`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,7 +905,7 @@ is **a gap**, rendered as one, and gate 12 covers that case.
- **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).
- They want measurement against a real canary, not a guess in a spec.
+ , not a guess in a spec.
- **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