oficina / home Public

Cite the verdict's confidence markings, and stop dismissing finding 6

The session that reconstructed the escritorio review from memory has
now read the primaries and retracted three things about its own
account: "REQUEST CHANGES" was its label rather than Fable's, the
recommendation was conditional ("if a merged list must exist, it is
Option B's mechanism without Option B's process") rather than "park
it", and several findings are marked Suspected rather than Confirmed.
None of the three had reached this spec except the paraphrased
recommendation, now corrected.

Findings now carry their own confidence marking. Flattening confirmed
into suspected is how a citation degrades back into the recollection it
replaced, so §2 says that out loud and marks finding 5's oracle as
suspected — treated as real anyway, because the answer costs one gate.

Finding 6 was wrongly filed as escritorio-specific and dismissed. It is
the opposite: "blast radius is bounded by what a service holds, not by
how many processes it runs in" is the verdict's own principle, written
to deny escritorio the per-team defence, and read forward it is the
argument for §3's amendment. If what a service holds is what bounds the
damage, a clause about rendering was never the protection and one about
storing is. Now quoted in both §2 and §3.

New §8.1 answers the objection ruling 5 attracts, since ruling 5 is the
most visible thing here: a named gap renders a team and app name, and
that discloses nothing, because the fan-out set is the person's own
directory rows and call 3 already hands them the same list. Covers the
verdict's finding 7 on guests.

It also records a consequence to expect rather than fix. The directory
is a hint with a drain lag, so the home will sometimes call an instance
the person left weeks ago, and ruling 5 will render a named gap for it.
Correct mechanism, wrong-looking page — the shape that gets
bug-reported and then fixed by widening something. The right fix is to
prune the stale row, never to suppress the gap.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev 49b80e18e6bbbb57c3fa6a41777fb4ddb107f7ea parent 64bfc70
1 file changed, +64 −6
  • docs/superpowers/specs/2026-09-05-activity-pull-design.md +64 −6
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 b2aef71..36034da 100644
--- a/docs/superpowers/specs/2026-09-05-activity-pull-design.md
+++ b/docs/superpowers/specs/2026-09-05-activity-pull-design.md
@@ -73,8 +73,15 @@ docs, calendar and mail into one list, apps posting entries opt-in, scoped to
pointers only. Three visibility options were put to a Fable review: **A**,
the app posts the ACL and escritorio filters locally; **B**, escritorio asks
each app at render time; **C**, A plus a priority push on revoke. The verdict
-was nine severity-ordered findings and a recommendation of *don't build it*.
-Paul parked it and put the home's identity layer first.
+was nine severity-ordered findings, each marked **confirmed** or
+**suspected**, and a **conditional** recommendation — not "park it", which
+was a later paraphrase: *"Do not build escritorio now; if a merged list must
+exist, it is Option B's mechanism without Option B's process."* Paul parked
+it and put the home's identity layer first.
+
+**Findings are quoted with their own confidence marking below.** The verdict
+distinguishes what it verified against a file from what it reasoned to, and
+flattening that distinction is how a citation degrades into a recollection.
**Both sources are in this repo**, because the citation was otherwise
uncheckable — they existed only as local files in a sweepable cache
@@ -97,12 +104,24 @@ The findings that bear on this design, and what happens to each:
| 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. |
-| 5 | **Option B's real costs:** a scriptless page blocking on four cold wakes, and a visibility endpoint that must not become an enumeration oracle. | **The first is designed for, in the shape the verdict itself pre-approved** — *"not disqualifying if escritorio renders what it has after a deadline and marks the rest 'waking', but that is a design the brief does not describe."* §6 and §8 describe it. **The second is answered in §5.1**, which the first draft of this spec missed. |
+| 5 | **Option B's real costs:** a scriptless page blocking on four cold wakes (*confirmed*), and a visibility endpoint that must not become an enumeration oracle (*suspected*). | **The first is designed for, in the shape the verdict itself pre-approved** — *"not disqualifying if escritorio renders what it has after a deadline and marks the rest 'waking', but that is a design the brief does not describe."* §6 and §8 describe it. **The second is answered in §5.1**, which the first draft of this spec missed. It is treated as real despite being marked suspected, because the cost of the answer is one gate. |
| 8 | **Blocked on §13 step 4**, not started. | **Still true.** §10. |
-Findings 6, 7 and 9 are about escritorio-as-a-process and mostly evaporate
-with the process: 6 (blast radius) has nothing to bound because no new store
-exists; 7 (guests) is handled in §5.2; 9's fourth option is this design.
+**Finding 6 does not evaporate — it argues for §3**, and an earlier draft of
+this spec wrongly filed it as escritorio-specific. Its principle, quoted
+there against escritorio's per-team defence:
+
+> **"Blast radius is bounded by what a service holds, not by how many
+> processes it runs in."**
+
+That is the same move §3's amendment makes, pointed the other way. Custody is
+what counts, not topology — which is exactly why *"never renders"* was never
+the protection and *"never stores"* is. It is the verdict's own principle
+doing this design's work, and it is the one sentence to cite if only one is
+cited.
+
+Finding 7 (guests) is handled in §5.2 and §8. Finding 9's fourth option is
+this design.
**The verdict recommended this mechanism.** Its words: *"a pull-based,
stateless page owned by one existing app… fetched from the sibling with the
@@ -222,6 +241,13 @@ and this document is a proposal against it.
**Why the amendment is small rather than a hole in the doctrine:**
+- **The escritorio verdict's own principle says custody is the thing**, not
+ topology: *"blast radius is bounded by what a service holds, not by how
+ many processes it runs in"* (finding 6, quoting home §2). It was written to
+ deny a per-team process the defence that its boundary made its holdings
+ safe. Read forward, it is the argument for this edit: if what a service
+ *holds* is what bounds the damage, then a clause about *rendering* was
+ never the protection, and one about *storing* is.
- **The exposure is bounded by where it lands.** The front door is on the
**directory origin** (`oficina.works`), which already renders
account-authored strings — team names — and already escapes them. A title
@@ -486,6 +512,38 @@ your apps just now"* — rather than rendering an empty feed. The empty state
and the everything-failed state are different pages, and a person can tell
which one they are looking at.
+### 8.1 A named gap discloses nothing, including to a guest
+
+Ruling 5 is the most visible thing in this document, so the objection it
+attracts is answered here rather than left to be derived. **A named gap
+renders a team and app name — does that leak, particularly to a guest?**
+
+The escritorio verdict's finding 7 (*suspected*) flags that guests fit no
+option cleanly: a guest must see their one item with no hint the team's other
+apps exist. Against ruling 5 that looks alarming. It is not:
+
+- **The fan-out set is the person's own directory rows** (§6), so the home
+ only ever names a team and app that person already has a row for.
+- **Call 3 already hands them the same list directly** — "every team across
+ every app, with `kind`". A named gap discloses strictly nothing the
+ directory would not answer if asked.
+
+So the gap is bounded by the directory, and the directory is the thing the
+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 (§9's call 2, third
+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.
+
+**That is a wrong-looking page with a correct mechanism behind it**, which is
+precisely the shape that gets bug-reported and then "fixed" by widening
+something. It is recorded here so the fix, when someone reaches for it, is
+the right one: **prune the stale directory row**, never suppress the gap.
+Suppressing gaps is ruling 5 in reverse.
+
---
## 9. Testing gates