oficina / home Public

Re-review: the fan-out set was stated two ways, and one of them leaked

Three blocking findings, and the first is a real disclosure.

§6 said the fan-out is "the selected team's apps"; §8.1 said it is "the
person's own directory rows". Those differ for a guest, and home spec
§11 rules which is right: tiles come from the viewer's own rows, never
from the label, because a guest in acme.docs would otherwise learn that
Acme also uses Sheets. Under §6's reading the guest's front door would
fan out to acme.sheets and ruling 5 would render "acme · sheets —
waking" — disclosing the forbidden thing through the honesty mechanism
that exists to stop silent failures. §8.1 had it right; §6 and §2.1 did
not. Gate 14 now covers the guest case explicitly.

The rescope also left §2's framing backwards. The verdict's finding 1
was that per-team gives three lists and answers the wrong question;
ruling 18 accepts three lists, so this design now takes the verdict's
SECOND path — the want declined in its original form — not its first.
§2.1 opened by calling finding 1 "the strongest objection available",
which it is not any more; it is the shape.

And §2.1's replacement justification answered an alternative nobody
proposed. It said the only other option was Sheets holding Docs'
grants, which would indeed be federation — but the verdict actually
recommended a stateless page in one app fetching from the sibling with
the viewer's identity, holding nothing. The argument that closes that
is the credential, not the knowledge: "with the viewer's identity"
means the home minting an aud=acme.docs assertion and handing it to
Sheets, which §11's first row calls full impersonation. Only the
credential origin mints, so only a page at the home can hold a
viewer-bound assertion per sibling without any instance holding one for
another. The knowledge claim was false anyway — call 4b lets an
instance learn a sibling exists, and the springboard shows the viewer.

Nine corrections. The minimum pull interval starts on a completed pull,
not an attempt, or the refresh affordance is dead during exactly the
window it exists for — and the design now says which of its three
timers governs what. §5.3's edge-logs leg was retracted on main, so the
§5 argument drops it and rests on history and referrers. The
one-process-two-hostnames assumption is load-bearing and established
nowhere, so §12 carries it. Plus the superseded §11 quotes, the stale
"all of a person's hosts", three unresolvable citations, the gate
preamble's wrong number, gate 20 sorting before 19, the clause count,
and the step-2 plan's location now that it is on main.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev 80d01f6ea99c3fb6a2a9450d700efb99f0d8f195 parent d11440b
1 file changed, +87 −35
  • docs/superpowers/specs/2026-09-05-activity-pull-design.md +87 −35

CI passed — run details

Intent

Human

<task-notification> <task-id>af3aee09163bd6edd</task-id> <tool-use-id>toolu_01KhJU6XmfZYzWaCRSFdE1gM</tool-use-id> <output-file>/home/paulca/.cache/tmp/claude-1001/-home-paulca-amadan-net-oficina-home/d63f5ac7-6b42-48c5-a13d-34e6faf8e44c/tasks/af3aee09163bd6edd.output</output-file> <status>completed</status> <summary>Agent "Fable re-review: activity pull" finished</summary> <note>A task-notification fires each time this agent stops with no live background children of its own. The user can send it another message and resume it, so the same task-id may notify more than once.</note> <result>VERDICT: CHANGES NEEDED The fourteen items from the first review are all present and, taken one by one, correct. The rescope itself left three things broken, and the per-team fan-out set now contradicts the home spec. Details below, all verified against `d11440b` and `main` at `84a6108`. ## What checks out - 1-6: §0/ruling 6/§2.1/§6/§8 are per-team; §7 has a refresh TTL and a "hard maximum age — after which rows are discarded regardless of pull outcome", with gate 20 driving "through failure"; ruling 7 makes participation "a flag in the home's compiled-in app registry (home spec §11), beside `provisionable` and `integrated`" — which matches main's registry fields exactly; §7 requires instances to send `no-store`, "a cacheable response is therefore a failed pull", gate 16 lists it, and the front door leans on main's gate 10; §2.1 says "for every person who rendered inside that window — not for one person", bounds the cache by entries and bytes, and records swap/core dumps as "NOT established… must be settled before this ships"; §12 moves hard max age, cache bounds and minimum pull interval into must-settle, and §6 specifies un-asked instances "render as a distinct gap with a refresh affordance". - 7-14: minimum interval with the billing reason ("instance-hours are billed to the instance's account, not to the person"); §5 opens with who mints; §6 hardens the client (full-body context, "redirects are never followed", TLS, bounded concurrency); "`since` comes only from this cache, and is never persisted"; §3 concedes §0's prose form; §3's clause says "and only to that person"; §12's AGENTS.md item is closed and `5a42f2d` is on main; gates run 11-20 with no collision against main's 1-10, and every `gate N` citation (11, 12, 16, 18, 19, 20; 1, 3, 4, 5, 6, 10) resolves. - Home §2.2 on main already carries the "Under amendment (ruling 18)" note and its strikethrough wording matches §3's quote. ## Blocking **1. The fan-out set is stated two ways, and one of them contradicts main's §11.** §6: "The selected team's apps, and only those… a team in three apps is three hosts." §2.1: "only the home knows which apps a team has instances in." But §8.1: "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." These differ for a guest, and main's §11 rules which is right: "Tiles come from the viewer's own rows, never from the label… a guest in `acme.docs` would learn that Acme also uses Sheets, which is another person's row and what gate 4 forbids." Under the §6 reading, a guest's front door fans out to `acme.sheets` and ruling 5 then renders "acme · sheets — waking" — the exact leak. §8.1's own finding-7 argument ("a guest must see their one item with no hint the team's other apps exist") only holds under the viewer's-rows reading. Fix §6 and §2.1 to "the viewer's own directory rows in the selected team", and add the guest case to gate 14 or 12. **2. §2's finding-1 row and §2.1's opening are incoherent after the rescope.** The row still says: "The ruling was reopened deliberately (rulings 1 and 2, Paul, 2026-09-05) — the first of the two paths the verdict named… §2.1 argues why a rendered page is not a held graph." The verdict's finding 1 was that per-team gives "three lists" and the cross-team list is forbidden; ruling 18 now *accepts* three lists. That is the verdict's second path — "the want is declined" — not the first. §2.1 still opens "The verdict's finding 1 is the strongest objection available to this design", which it no longer is, and main's §13 step 10 explicitly asked for "its §5.4 reopening revisiting, not just its query changing." The "Why §5.4 is not breached" subsection survives on its own terms, but the framing around it says the opposite of what the design now does. **3. §2.1's replacement justification answers an alternative the verdict did not propose.** It says the only alternative is "Sheets' home page gaining a Docs section — requires Sheets to hold Docs' grants. That is… federation." But the verdict's recommendation (quoted in §2 itself) was "a pull-based, stateless page owned by one existing app… fetched from the sibling with the viewer's identity" — Sheets pulling from Docs at render, holding no grants. As written, §2.1 is answerable in one sentence and the departure from the verdict is unjustified. The argument that actually closes it is in the design's own §11: only the credential origin mints assertions, so Sheets pulling from Docs "with the viewer's identity" means the home handing Sheets an `aud=acme.docs` assertion for you — an instance holding a bearer for you at a sibling, which §11's first row calls "a cross-instance bearer… full impersonation". Knowledge is the weak reason; the credential is the real one. Also, "only the home knows which apps a team has instances in" is false as stated — an instance can learn a sibling's existence via main's call 4b, and the springboard shows it to the viewer; what is uniquely the home's is minting plus which of a team's instances *you* have rows in. ## Required corrections 4. §9 preamble: "`main` took gate 11 for no-store (ruling 22), so these start at 11." False — main's no-store is **gate 10** and main has ten gates; §7 of this design says "the home spec's gate 10" itself. The 11-20 range is right, the sentence isn't. Also gate 20 is listed before 19. 5. Citations that don't resolve: §5 "(§16's gate)" should be gate 16; §3 "(§9 gate 1)" reads as home §9, the contract — it means home §12 gate 1; §2.1 "§11 lists cross-person aggregation as never licensed" — neither §11 does; it's this design's §3 and home §2.2. 6. §5 quotes §5.3's "last line" as: "Delivery is an auto-POST, not a query string, so assertions stay out of edge logs, browser history and referrers", then argues "Headers are not written to edge access logs the way query strings are." Main's §5.3 retracted the edge-logs reason on 2026-09-05: "No platform request log carrying paths was found… the third was inherited rather than verified." Quote the current line and drop that leg; the history and referrer legs still carry the argument. 7. §5: "Say which this is: **one process serving two hostnames**, so the signing key's exposure is unchanged." Nothing on main establishes this — AGENTS.md says one binary, §5.3 gives the directory origin "its own" `jti` store as a verifier, and the skeleton handles no hostnames. It is load-bearing (if the origins ever split, the directory needs a mint call and the key story changes), so it needs a citation or a place in §12. 8. §2.2(a): "fire an authenticated, timestamped request at all of a person's hosts at once" — stale; per-team makes it the selected team's hosts. The guest/removed-row sentence after it survives. 9. §8 "§11 stands: your teams grouped by app, and a New button" and §2.2's same quote cite the superseded §11. Main's §11 is "one page: a grid of apps, with a team switcher as a dropdown in the header" for the selected team (ruling 17). 10. §6's minimum pull interval "enforced even on an explicit refresh" is not related to the waking state. If a timed-out pull starts the interval, "acme · sheets — waking, refresh" cannot refresh during exactly the window the affordance exists for. Say whether the interval starts on any attempt or on a completed pull, and how it relates to §7's refresh TTL — the design now has three timers (refresh TTL, hard max age, minimum interval) and never says which one governs when. 11. §10: "Step 2 (sign-in) has a plan on `docs/step-2-signin-plan`" — the plan is on main as `docs/superpowers/plans/2026-09-03-home-signin.md` (`b92c9b8`). 12. Small: §6 "not the six a cross-team merge over three teams would have been" — three teams in three apps is nine; say what six counts. §2.2 "§9's six calls" — main's §9 now has call 4b as well. §0 "Only one clause changes" vs §3 also moving §0's gloss — say two. Nothing else in §5's oracle sections, §8's rendering, or §11's not-built list is broken by the rescope; they are per-person mechanisms and carry over. On the pressed question: per-team does not make the feature weak enough for an app to do — the merge across a team's apps needs a viewer-bound credential at each sibling, and only the home can mint one — but §2.1 does not currently say that, and the argument it does make is the wrong one.</result> <usage><subagent_tokens>87534</subagent_tokens><tool_uses>14</tool_uses><duration_ms>213394</duration_ms></usage> </task-notification>

Attributed to this commit: 41.4 g CO₂e (5182642 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 30371b7..522c22c 100644
--- a/docs/superpowers/specs/2026-09-05-activity-pull-design.md
+++ b/docs/superpowers/specs/2026-09-05-activity-pull-design.md
@@ -58,8 +58,8 @@ inside the one rule instead of against it:
- **The app that owns the grants answers the question about the grants.** No
authorization model at the home (§2.2), no copied ACL to drift, no revoke
tail.
-- **Only one clause changes** (§3), and it changes from *never renders* to
- *never stores*.
+- **Two clauses change** (§3): §2.2's verb, from *never renders* to *never
+ stores*, and §0's prose gloss, which now reads as *never retains*.
What it costs is latency, honestly rendered. §6 and §7.
@@ -118,7 +118,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.** Per-team aggregation gives a person in three teams three lists. 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."* 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."* 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. |
| 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. |
@@ -152,8 +152,15 @@ existing app, and this puts it at the home. §2.1.
### 2.1 Why the home, and why a rendered page is not a held graph
-The verdict's finding 1 is the strongest objection available to this design,
-so it gets answered rather than routed around.
+The verdict's finding 1 said a per-team feed gives "three lists" and answers
+a different question than the one asked. **Ruling 18 accepts three lists**,
+so this design now takes the verdict's *second* path — the want is declined
+in its original form — rather than its first. An earlier draft of this
+section opened by calling finding 1 "the strongest objection available"; it
+is no longer an objection at all, it is the shape.
+
+What still needs answering is §5.4, and home spec §13 step 10 asks for that
+reopening to be revisited rather than left standing.
**Why the home and not an app — re-justified, because Paul's per-team
ruling retired the argument this section used to make.** That argument was:
@@ -161,14 +168,27 @@ only the home knows every team you belong to, so only the home can answer
*"my things"* rather than *"this team's things"*. Per-team scope concedes
exactly that question, so the argument is gone and is recorded here as gone.
-What survives is narrower and still uniquely the home's: **only the home
-knows which apps a team has instances in.** A team's feed merges Docs,
-Sheets and Calendar; no one of them knows the others exist for that team,
-and the alternative — Sheets' home page gaining a Docs section — requires
-Sheets to hold Docs' grants. That is the federation §2.2 of the home spec
-forbids outright, and it is a worse outcome than the one this design was
-accused of. So the merge belongs at the home, at a narrower scope than
-before.
+What survives is narrower and still uniquely the home's — **and it is the
+credential, not the knowledge.**
+
+An earlier draft of this paragraph said "only the home knows which apps a
+team has instances in", and answered an alternative nobody proposed (Sheets
+holding Docs' grants, which would indeed be federation). Both are wrong. The
+knowledge is not exclusive: home spec §9's call 4b lets an instance learn a
+sibling exists, and the springboard shows the viewer anyway. And the
+verdict's actual recommendation was not grant-holding — it was "a
+pull-based, stateless page owned by one existing app… fetched from the
+sibling with the viewer's identity", which holds nothing.
+
+**That alternative fails on the credential.** "With the viewer's identity"
+means Sheets presenting something at `acme.docs` that speaks for *you* — so
+the home would have to mint an `aud=acme.docs` assertion and hand it to
+Sheets. §11's first row names that exactly: a cross-instance bearer is full
+impersonation, and it is the one thing this design refuses to build. Only
+the credential origin mints, so only a page at the home can hold a
+viewer-bound assertion per sibling without any instance ever holding one for
+another. That is the whole of why the merge lives here, and it survives the
+rescope untouched — it was never an argument about scope.
**Per-team is also cheaper on every axis this design worries about.** §6's
fan-out drops from every instance a person can reach to the apps in one
@@ -193,8 +213,8 @@ earlier draft admitted: for the lifetime of one HTTP response, and for the
RAM cache's TTL, the home's process memory holds a cross-app list of titles
**for every person who rendered inside that window** — not for one person.
That is a cross-person, cross-app set of recent titles, resident in the same
-process that mints sessions. §11 lists cross-person aggregation as never
-licensed; this builds it as a data structure while forbidding it as a
+process that mints sessions. §3's amendment lists cross-person aggregation
+as never licensed, and home spec §2.2 forbids the console it would build; this builds it as a data structure while forbidding it as a
feature, and the distinction only holds because nothing reads across the
per-person keys. **So the cache is bounded**: a maximum number of person
entries and a maximum bytes per person, both named in §12, evicting oldest
@@ -225,7 +245,7 @@ to pull on — when it gives way it takes §3 with it. The accurate form:
**(a) A passive map becomes a broadcast.** §6's knowledge sits still today.
Call 7 makes every front-door render fire an authenticated, timestamped
-request at all of a person's hosts at once. Each instance thereby learns
+request at the selected team's hosts that the viewer has rows in (§6). Each instance thereby learns
*"this person opened their front door at 09:14:03"* — including on mornings
they never open that app, and from a host they may have been removed from
whose directory row has not drained.
@@ -241,7 +261,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.** §9's six calls 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
@@ -312,7 +332,7 @@ and this document is a proposal against it.
preview cache, an index. Storage is what makes it that. A string that
exists for the length of one HTTP response, in RAM, addressed to the one
person entitled to it, does not.
-- **It is testable.** "Never stores" has a grep behind it (§9 gate 1).
+- **It is testable.** "Never stores" has a grep behind it (home spec §12, gate 1).
"Never renders" had no test at all, which is a fair sign it was a
statement of intent rather than a mechanism.
@@ -385,9 +405,13 @@ the **credential origin**, and the front door is on the **directory origin**,
which obtains assertions like any other consumer. The fan-out therefore mints
N assertions per render from a page that is not the minting origin. Say which
this is: **one process serving two hostnames**, so the signing key's exposure
-is unchanged from §5.3 and no cross-origin mint call exists — and note the
+is unchanged from §5.3 and no cross-origin mint call exists. **Nothing on
+main establishes that today** — AGENTS.md says one binary, and §5.3 gives the
+directory origin its own `jti` store as a verifier — so §12 carries it as a
+thing to settle. It is load-bearing: split the origins and the directory
+needs a mint call and a different key story — and note the
consequence, that the directory origin is now also the surface parsing N
-consumer-supplied JSON bodies per render (§16's gate).
+consumer-supplied JSON bodies per render (gate 16).
**The pull carries a §5.3 assertion, unchanged.** Ed25519, `aud` bound to the
answering instance's app and host, `sub` the person ref, `iss` the credential
@@ -404,8 +428,9 @@ travelled." **That is false, and §5.3's own last line is the counter-example:**
logs, 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 three things an
-auto-POST does. Headers are not written to edge access logs the way query
-strings are, there is no browser involved so there is no history, and there
+auto-POST does — with one leg removed: §5.3's "edge logs" reason was
+retracted on main on 2026-09-05, since no platform request log carrying
+paths was found. There is no browser involved so there is no history, and there
is no `Referer` on a server-to-server call. **What must never happen is the
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.
@@ -476,9 +501,18 @@ nothing"), so they have no front door and no rows. Correct by construction.
## 6. Fan-out, and the cost this design pays
-The selected team's apps, and only those — per Paul's per-team ruling, a
-team in three apps is three hosts, not the six a cross-team merge over three
-teams would have been. The home fans out in
+**The viewer's own directory rows in the selected team — not the team's
+apps.** The distinction is a leak, not a nicety, and an earlier draft of this
+line got it wrong. Home spec §11: "Tiles come from the viewer's own rows,
+never from the label… a guest in `acme.docs` would learn that Acme also uses
+Sheets, which is another person's row and what gate 4 forbids." Fanning out
+to the team's apps would ask `acme.sheets` on that guest's behalf, and
+ruling 5 would then render "acme · sheets — waking" on their front door —
+disclosing the very thing §11 forbids, through the honesty mechanism. §8.1
+already had this right; §6 and §2.1 did not.
+
+So a member of a team in three apps is three hosts; a guest with one row in
+that team is one host. The home fans out in
parallel, with a per-request deadline, and renders what arrived.
**Zero JS (§11).** One server-side fan-out, no progressive loading, no
@@ -509,8 +543,14 @@ a few seconds". So:
guest with one row in `acme.docs` would otherwise wake Acme's instance on
every front-door load, at Acme's expense. So there is a **minimum pull
interval per (person, instance), enforced even on an explicit refresh**;
- the refresh affordance re-renders from cache when the interval has not
- elapsed, and says so.
+ the interval starts on a **completed** pull, not on an attempt, so a timed
+ out instance can be retried immediately — otherwise the refresh affordance
+ would be dead during exactly the window it exists for.
+
+**Three timers, and which governs.** The **minimum pull interval** decides
+whether a pull may be attempted at all. Within that, the **refresh TTL**
+decides whether the home bothers. The **hard maximum age** (§7) overrides
+both: past it the rows go, whatever the other two say.
Neither is fixed here. Both are **mitigated and disclosed**:
@@ -590,7 +630,7 @@ TTL — a test against the refresh TTL proves the easy half.
## 8. What the front door renders, including when it cannot
-§11 stands: your teams grouped by app, and a New button. This adds a recency
+home spec §11 is now one page — a grid of apps for the selected team, with a team switcher in the header (ruling 17). This adds a recency
section above or beside it — **layout is the front-door session's call, not
this spec's.**
@@ -659,8 +699,8 @@ Suppressing gaps is ruling 5 in reverse.
## 9. Testing gates
-Numbered from the home spec's **ten** — `main` took gate 11 for no-store
-(ruling 22), so these start at 11.
+Numbered from the home spec's **ten**. `main` has gates 1-10, the tenth
+being no-store (ruling 22), so these start at 11.
11. **No pulled content at rest — driven through the failure paths, not the
happy one.** A full front-door render against seeded instances leaves no
@@ -693,6 +733,14 @@ Numbered from the home spec's **ten** — `main` took gate 11 for no-store
14. **Two-person isolation on the pull.** Person A's assertion returns only
A's rows; a cached answer for A never renders on B's page. Extends
gate 4.
+
+ **And the guest case, which is the one that bites.** A guest with a row
+ in `acme.docs` only: assert the render fans out to `acme.docs` and to
+ **nothing else in that team**, and that no gap naming `acme · sheets`
+ appears. Fanning out by the team's apps rather than the viewer's rows
+ would disclose that Acme uses Sheets through ruling 5's honesty
+ mechanism — home spec §11's gate 4 concern, arriving by the back door.
+
15. **The credential origin renders no pulled content.** Extends gate 5.
16. **A hostile instance response cannot hurt the front door.** Oversized
body (cap enforced), a `url` on another host (rejected), an unknown
@@ -706,16 +754,17 @@ Numbered from the home spec's **ten** — `main` took gate 11 for no-store
scripts stripped. Extends gate 6.
18. **Not an oracle** (§5.1). A person ref with no grant and a person ref
that does not exist produce byte-identical responses from an instance.
+
+19. **One assertion, one host.** The fan-out mints a separate assertion per
+ instance, and a test asserts that no two requests in one render carry the
+ same `aud`. §11's first row is the reason this is a gate and not a note.
+
20. **The hard maximum age is enforced through failure.** Seed a cached
answer, make the instance fail, advance past the hard maximum, and
assert the rows are gone and a gap is rendered — not that they survive
because the pull failed. A test against the refresh TTL proves the easy
half; this is the half that bounds the lie (§7).
-19. **One assertion, one host.** The fan-out mints a separate assertion per
- instance, and a test asserts that no two requests in one render carry the
- same `aud`. §11's first row is the reason this is a gate and not a note.
-
---
## 10. Sequencing, and what blocks it
@@ -726,7 +775,7 @@ Numbered from the home spec's **ten** — `main` took gate 11 for no-store
- **§13 step 4** — the directory and person refs. The home cannot know which
instances to ask, and `sub` has nothing to carry.
-Step 2 (sign-in) has a plan on `docs/step-2-signin-plan` and cannot begin
+Step 2 (sign-in) has a reviewed plan on main at `docs/superpowers/plans/2026-09-03-home-signin.md` (`b92c9b8`) and cannot begin
until `HOME_DEK` is minted, which only Paul can do.
**This is spec-now, build-later, and the slot is after step 4** — a step 10 in
@@ -795,6 +844,9 @@ is **a gap**, rendered as one, and gate 12 covers that case.
The fan-out budget's *number* can wait; its behaviour at the limit is
specified in §6 and is not open.
+- **Whether the credential and directory origins are one process.** §5
+ assumes they are, and the signing-key story depends on it. Nothing on
+ 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).