oficina / home Public

Ledger · suite-amendment

Human

<task-notification> <task-id>a4d29f9ead14e710e</task-id> <tool-use-id>toolu_011Jfky4sZuhK5o7EG5672F9</tool-use-id> <output-file>/home/paulca/.cache/tmp/claude-1001/-home-paulca-amadan-net-oficina-home/d63f5ac7-6b42-48c5-a13d-34e6faf8e44c/tasks/a4d29f9ead14e710e.output</output-file> <status>completed</status> <summary>Agent "Fable re-review: suite-amendment" 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** All seven blocking fixes landed as described, and the non-blocking ones too (seed verb, per-team epoch + 404, gate 10, Rejected column, AGENTS.md rules 2 and 3, §6's directions — call 7 home→instance, call 8 instance→home, now correct). Verified at `/home/paulca/amadan.net/oficina/home/.claude/worktrees/suite-amendment/docs/superpowers/specs/2026-08-30-home-design.md` against `2b68301` and the doctrine. But two of the fixes introduced new false claims, and ruling 23 falsifies more sentences than either commit reached. ## Blocking **1. The roster step-up is not a "different mechanism" from §14's row — it is that row.** (§10 line 996, §14 line 1383.) The directory origin holds no credential; §4 says it "obtains its session by exchanging an assertion, the same mechanism instances use". So its session's `AuthTime` is set by the exchange, and when `sessions.Fresh` fails the only way to make it pass is a new exchange in which the credential origin *forces re-verification* rather than minting from its standing session. rastrillo v0.22.0 says exactly this in `RequireFresh`'s doc (`/home/paulca/go/pkg/mod/github.com/carlosframework/rastrillo@v0.22.0/sessions/sessions.go:334-345`): a stale session "redirects to SigninPath with return_to plus reauth=1 … re-verifying calls SignIn, which rotates the session with a fresh AuthTime". At the directory origin, SigninPath is the assertion exchange and `reauth=1` is the `max_age`-style parameter. Same mechanism, different caller. The doctrine says so in §5.4 ("home §14 names it; this is its trigger") and A.1 ("In §14 move *step-up for instances* to built-when: *roster writes*"), in both rev 4 and rev 5. The branch does the opposite and records no divergence — under doctrine §10 that is a bug in the app spec. Fix: take A.1 as written, sequence the round trip with the roster, name the credential origin's reauth parameter, and delete "different caller, different mechanism" in §10 and §14. (§3.2's `sessions.Fresh` citation is correct for the show-codes-once page — that page is at the credential origin, where the session was minted by real authentication. It does not transfer.) **2. The §2 blast-radius sentence (lines 167-176) has two false clauses.** (a) "a compromised **home** costs … It still costs no *per-item* grant" — widening *directory* to *home* is the precise error the doctrine's ruling 13 Rejected column names, and it is false for the credential-origin tier, which §13a says "can sign in as anyone" and therefore reads every individually granted item as that person. The clause must say *directory origin*. (b) "until an owner notices" asserts a bound §13a itself denies four screens later ("neither does a directory row or a notification: those are what honest code leaves behind"). The owner's only remedy runs through the compromised origin. The honest bound is the duration of the compromise. **3. §5.5 — promote from non-blocking.** "decides capability **entirely from its own grants** … a person with no grant … gets nothing" (lines 601-604). Ruling 23's own Rejected column names "§5.5 … as they stood" as the overturned status quo, and §5.5 stands untouched. It is the instance-side section Docs and Sheets build from; as written a suite instance ignores call 8. Two sentences: team standing from the call-8 copy (suite ruling 5, §5.3), per-item from its own grants; a member with no grant reaches team-rung items. **4. §7's closing paragraph is still pre-23.** Line 754: "the home holding a person↔resource entitlement **is** what §2.2 forbids" (present tense — the roster is one). Lines 810-812: "It never forms an opinion about whether a person is entitled to anything" — contradicts the paragraph twelve lines above it ("Ruling 23 forms one deliberately … holds and enforces"). **5. AGENTS.md disagrees with the spec at the headline, and its own rule is "fix it in the same commit".** `/home/paulca/amadan.net/oficina/home/.claude/worktrees/suite-amendment/AGENTS.md`: the falsifiable form (lines 31-34) still reads "that some person belongs to a particular set of them" — spec §0 now says "which people belong to which team, and which of them own it", and the owner class is the whole cost of ruling 23. The boundary test (lines 129-136) states (a) closed-set with no `roster_epoch` exception — a builder following it rejects a claim §5.3 puts on every assertion. Line 316 "loses credentials, which do not rebuild" omits the roster (§10). Rules 2 and 3 are fine. **6. Ruling 15's restated ground does not hold — this is where the resources-we-own / instance's-items line leaks.** §2.2 lines 243-246: "a policy the home would have to enforce **on an instance's behalf** … which is the instance's decision on its own request path." Authentication is the home's (ruling 1: one RP), the team is the home's (17, 23), and the home knows `aud`→team via `team_instances`, so a per-team 2FA floor is enforced at assertion minting — a policy on a *resource the home owns*, which the new line permits. An instance demands nothing at authentication; it exchanges an assertion. Consequently §5.2's "a home-side policy contradicts §2.2" and §14's "argued past §2.2" are now false. Ruling 15's *outcome* stands on mechanics — `Gate` is `Enrolled(sess.Subject)`, per-account, and slice scope — so re-ground it there rather than on §2.2. (§14's "a sixth call" has been stale since call 6 existed.) The line holds everywhere else I pressed it: the §2.2 roster bullet, both §7 paths, ruling 19's narrowing, §6. ## Non-blocking — status confirmed, with sharpening - **§10 recovery circularity stays non-blocking**, but say the mechanism: no call carries an instance's roster copy *to* the home (8 is a pull), so "the instances' own copies" means an operator reads them out and re-seeds via §10's operator verb; after a total loss the "owner's confirmation" presupposes an owner the home no longer knows, and the seed verb is the break. Same wording is in doctrine §5.3 — file it there too. - **§13 sequencing stays non-blocking.** The roster (table, seed verb, owner write + step-up, call 8) has no step; doctrine §12 step 4 puts it on top of home step 4. "Step 4 must land before either ships" gains a second reason in suite shape (ruling 5); lone shape is unaffected (suite §12). - **Doctrine is now revision 5** (`d55fa15`, on `origin/main` after `210312f`). Citing rev 4 is explicitly allowed by gate 9's one-revision grace and the rev-5 status says the home's citation "is correct as it stands". One rev-5 change touches the home: §5.3 scopes the epoch exception to the call-8 path and says a call-7-answering instance must not act on the claim. §5.4 should carry that scope when next touched. - **Roster add writes directory rows** (doctrine §5.4: "announces them to every `team_instances` row's directory entry"). Home §6/§9/§10 have call 2 as the only directory writer. Unstated for the home builder; removal's effect on those rows is unstated in both documents. - Editorial: ruling 23's Rejected column repeats "which the doctrine's own first revision proposed"; §1's blank line before `---` was removed (restore it — main had it); §10's memberships paragraph splits "by a team owner … or seeded by the drain" across a five-line parenthetical (reflow with fix 1); §2 line 157 "the federation story §3 refuses" — §3 refuses nothing and "federation" is retired vocabulary (pre-existing on main). Fixes 2, 3, 4 and 5 are a few sentences each. Fix 1 is a ruling-consistent reversal of last round's change and should be recorded as such. Fix 6 changes a ground, not an outcome.</result> <usage><subagent_tokens>156065</subagent_tokens><tool_uses>18</tool_uses><duration_ms>373830</duration_ms></usage> </task-notification>

Human

<task-notification> <task-id>a66a4b34e6f35ba11</task-id> <tool-use-id>toolu_017FkQoq2dJorBy5qKECCwir</tool-use-id> <output-file>/home/paulca/.cache/tmp/claude-1001/-home-paulca-amadan-net-oficina-home/d63f5ac7-6b42-48c5-a13d-34e6faf8e44c/tasks/a66a4b34e6f35ba11.output</output-file> <status>completed</status> <summary>Agent "Fable review 3: suite-amendment" 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** Items 1, 3, 4 and the epoch scoping verify at source and are right. Item 6's central fact is right but its ground still has a false clause and now contradicts two other sections. Item 2's sentence is not yet true at the directory tier. Item 5 misses one sentence in AGENTS.md that ruling 23 falsifies. And there are three more pre-existing sentences ruling 23 (via the doctrine) falsifies that no pass has caught. All read at `3de8f0b`; rastrillo checked at the v0.22.0 module cache; doctrine at `origin/main` (revision 5). ## The six, verified **1. §14 step-up reversal — correct.** `sessions.Fresh` (sessions.go:366) measures `AuthTime`, falling back to `At`. The directory origin's session is minted at the assertion exchange (§4), so both reference the exchange, not the credential. A stale directory session redirected to the credential origin would get a new assertion minted from the credential origin's *standing* session and pass `Fresh` with no re-verification — which is exactly why the exchange needs a `max_age` parameter the credential origin honours. That is §14's row's mechanism; only the caller differs. Doctrine §5.4 and A.1 say the same. The §5.2 distinction holds: on the credential origin `AuthTime` is set by real verification (auth/handlers.go:138, passkey.go:553, recovery.go:151). **3. §5.5 — correct** against doctrine §4.2/§5.3. **4. §7 closing — correct**, but see finding A3 below: another §7 sentence about ruling 15 is now contradicted. **5. AGENTS.md** — the three named edits match the spec. One pre-existing sentence does not (finding C). **6. Ruling 15** — `Gate` calls `h.Enrolled(sess.Subject)` → `SELECT COUNT(*) FROM passkey_credentials WHERE subject = ?` (passkey.go:194-195, 348-353). Per account, no team in scope: verified. The rest of the ground is where it fails a third time — finding A. ## Blocking **A. Ruling 15's ground, third attempt — one false clause and two contradictions.** - A1. *"a per-team enrolment state that slice one does not have"* (§2.2 line 259, §5.2 line 511, §14 line 1412) is not what a floor needs. Under ruling 1 enrolment is per person at one RP; there is no per-team enrolment, and per-team compliance is `memberships ⋈ passkey_credentials`, both of which exist. What is actually missing, mechanically: `Gate` demands the ceremony only when the account is *enrolled* — an un-enrolled member of a floored team returns `(false, nil)` and sails through — so a floor needs a *policy-driven* demand at assertion minting (step-up if the session's `Method` lacks the factor, forced enrolment if none), plus the per-team policy record. Name that instead. - A2. §2.2 now says the floor "would be enforced at assertion minting, on a resource the home owns". Then §14's trigger still reads *"either a call carrying team policy is built … or rastrillo grows a per-team notion"* and §5.2 still says *"no call carried a team's policy"*. If the home enforces at minting, no call carries policy anywhere — the owner writes a policy record at the directory origin, the roster write's shape. The trigger is the abandoned instance-side model surviving in the row that is supposed to carry the objection. - A3. §7 line 782-784 still says *"ruling 15 rejected a per-team 2FA floor — a policy with no content in it at all — on exactly these grounds"* (§2.2's authorization-model clause). §2.2 now says ruling 15 "stands on mechanics, not on §2.2". Same document, opposite claims. Ruling 15's own Rejected column on 2026-08-30 was buildability, so the honest fix is to drop the sentence, not re-date it. **B. §2's sentence is still false at the directory tier — the bound is a floor, not a ceiling.** *"for as long as the compromise lasts"* (line 174-175). A compromised directory origin has two ways in: forge the call-8 response (no row at rest — this is what §13a's "what honest code leaves behind" means: the row is not *guaranteed*) or write a `memberships` row. A forged roster outlives the compromise by up to one roster TTL in each instance's copy; a written row outlives it **until an owner removes it** — which is precisely the "until an owner notices" clause that was just deleted. §13a was misread: it says the row isn't evidence a compromise must leave, not that it never persists. Honest sentence: *"for the duration of the compromise, plus one roster TTL for any forged copy, plus — for any row it wrote — until an owner finds and removes it."* Credential tier is right. **C. AGENTS.md rule 3, line 101-102:** *"Sharing is a grant on the instance — member or guest — never a copy."* Pre-existing on main, false since ruling 23: member standing is the home's roster, not an instance grant (spec §5.5, §6 "a directory row never grants team standing whatever its `kind` says"). This is the exact §5.5 status-quo error, surviving in the binding file. Fix: *"Sharing across teams is a guest grant on the instance, never a copy; team standing is the roster (§5.5)."* **D. "The falsifiable form is unchanged" — three places, all now contradicted a few lines later.** Spec §0 line 30-31 and line 53 ("is the test that matters and is unchanged"); AGENTS.md line 24. Written for ruling 18; ruling 23 changed the form and put something new at rest, and both files say so in the next paragraph. Scope them: "unchanged by that amendment". **E. §2.1 "Existing sessions keep working"** (line 201) is false past the roster TTL. Doctrine §5.3: past the TTL with no successful refetch, team standing stops "**including people already in a session**"; only individual grants survive. §2.1's whole outage analysis and the RTO's sizing rest on "new sessions only". Same defect at §2 line 191: "Losing it costs a convenience, not access — for the directory. Sign-in is the exception" — the roster is a second exception, and §10 already concedes it for total loss. **F. §9 calls 2 and 5 do not carry the roster epoch.** Doctrine §5.3: "a roster epoch carried in every call-2, call-5 and call-8 response, and in the assertion". The home owns calls 1–6 (doctrine §6); §9 row 2 returns "the person ref and the pepper epoch", row 5 nothing. A call-2 builder reading §9 omits two of the doctrine's four staleness carriers. Add it to both rows or record the divergence. **G. A member with no per-item grant has no springboard tile — so "roster-add-writes-directory-rows" is no longer merely non-blocking.** §11: tiles come from the viewer's directory rows. §9: call 2 fires when "a grant changes". §5.5 as now fixed: a member with no per-item grant reaches team-rung items. So a pure member has team standing and no directory row for that team, and the front door hides the team from them. Doctrine §5.4 answers it (a roster add "announces them to every `team_instances` row's directory entry"); the home spec neither adopts nor rejects it, and §6's writer story is call 2 only. Either adopt the doctrine's sentence in §10 (with the matching delete on removal), or have §11 render member tiles from `memberships ⋈ team_instances` and keep directory rows for guests — the second is cleaner, since the home owns both tables. One paragraph either way. ## Non-blocking - §13 has no step for `memberships`, call 8, the seed verb or the now-triggered step-up; §13a's "waits on step 4" and doctrine §12 step 4 make it derivable. But the closing paragraph's ship gate ("step 4 must land before either ships") now also needs call 8 for suite-shape Sheets/Docs. - §13a cites revision 4 / `210312f` while §5.4 consumes a revision-5 rule. Gate 9 passes on the commit; the citation is still dishonest about what was read. - §10 lines 1030-1031: the inserted parenthetical severs "or seeded by the drain…" from its antecedent — it now reads as "§5.4 says '…trigger', or seeded by the drain". - §1 ruling 23 Rejected column: "which derived the roster from call-2 directory rows, which the doctrine's own first revision proposed" — duplicated phrase. - No home-side gate exercises the roster write (step-up required, call 2 refused, other-team 404). §12 has ten gates and none on the most security-load-bearing new write; suite gates 5 and 8 are instance-side and schema-side. - §7 says the sibling's "first owner … arrive as sealed queue payload exactly as a fallback requester's do"; §10 says a sibling drain seeds nothing. Reconcile or say the field is ignored. - §5.2: with ruling 23 a team whose only owner loses passkey and codes cannot write its roster; the seed verb is the de facto remedy and the operator-reset trigger arguably fires earlier. Worth a sentence. - §15 Open, Docs' re-review: add calls 7 and 8 to the list. Files: `/home/paulca/amadan.net/oficina/home/.claude/worktrees/suite-amendment/docs/superpowers/specs/2026-08-30-home-design.md`, `/home/paulca/amadan.net/oficina/home/.claude/worktrees/suite-amendment/AGENTS.md`. Nothing changed, committed or merged.</result> <usage><subagent_tokens>166045</subagent_tokens><tool_uses>19</tool_uses><duration_ms>422746</duration_ms></usage> </task-notification>

Human

<task-notification> <task-id>a4a5a520a7e0368cc</task-id> <tool-use-id>toolu_01CoLEJLmVVKq3aWo22BPVF9</tool-use-id> <output-file>/home/paulca/.cache/tmp/claude-1001/-home-paulca-amadan-net-oficina-home/d63f5ac7-6b42-48c5-a13d-34e6faf8e44c/tasks/a4a5a520a7e0368cc.output</output-file> <status>completed</status> <summary>Agent "Fable review 4: suite-amendment" 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 — one blocking item, in the §11 paragraph that fix G introduced. Everything else on this branch is right at source; if that paragraph is corrected I would approve without another pass. Verified against `/home/paulca/amadan.net/oficina/home/.claude/worktrees/suite-amendment` at `b0966ea`, the doctrine at `origin/main` (revision 5, `d55fa15`; revision 4 `210312f` checked directly), and rastrillo `v0.22.0` (`passkey/passkey.go`, `sessions/sessions.go`, `auth/handlers.go`). ## The seven fixes **A — ruling 15's ground.** Right now. At v0.22.0 `Gate` is literally `enrolled, err := h.Enrolled(sess.Subject); if !enrolled { return false, nil }`, and `Enrolled` is `SELECT COUNT(*) FROM passkey_credentials WHERE subject = ?`. So an un-enrolled member of a floored team does pass untouched — the spec's sentence is the source. "At assertion minting" is the correct locus, not sign-in: sign-in is team-less, and the team is only known at minting through `aud → team_instances`. The `Method` check is workable: the Gate flow mints `p.method + "+passkey"`, a step-up mints `"passkey"` (lines 336 and 552). "Force enrolment if none" is also right — `StepUpFinish`/`SignInFinish` look up the credential by subject and there is none to assert against. §5.2, §14's row and §7's withdrawal all agree with each other. The Rejected column ("neither spec can build") matches. Done. **B — §2's bound.** True at both tiers. The bullet is explicitly the directory tier and defers the credential tier to §13a. The two tails match doctrine §5.3 (a forged copy lasts at most one TTL "on an instance that admits nobody"; a written row is a row). The reading of §5.5's "what honest code leaves" is correct: the doctrine's point is a compromise need not write a row, not that a written one evaporates. One imprecision, not blocking: the removal tail also carries a TTL (after the owner removes the row, an instance nobody signs in to serves it for up to one more TTL), so strictly it is "until removed, plus one TTL". "Floor, not ceiling" covers it. **C — AGENTS.md rule 3.** Correct against doctrine ruling 2 ("a guest is a scoped member of the granting team; a link share moves nothing") and §6. **D — the three "unchanged" claims.** Scoped. One awkwardness: §0's "The falsifiable form below is unchanged by that amendment — ruling 23 later did change it, and says so — nothing new goes at rest." The trailing clause reads as a standalone claim, which ruling 23 falsifies (`memberships` is new at rest). Nit: "…and says so — that amendment put nothing new at rest." **E — §2.1 and §2's "convenience".** Both match doctrine §5.3 (stale past TTL stops team standing for existing sessions too). Right. **F — calls 2 and 5 carry the roster epoch.** Verified in §9. Right. **G — the member's tile.** The join is the right mechanism, but see blocking below. **Non-blocking taken:** gate 21 is sound and correctly numbered. §13a's citation is accurate — `210312f` is revision 4 and does *not* contain the call-8 scoping (grep confirms), so naming it as a rev-5 rule consumed under gate 9's grace is exactly right. §15's Docs re-review names calls 7 and 8. ## The four questions **Ruling 15's ground — is it right now?** Yes, see A. Nothing I would change. **§2's sentence at both tiers?** Yes, see B. **Is the §11 tile join right? Does it leak?** The join is right and does not leak. A member seeing every `acme.*` instance is what doctrine §5.4 already intends ("their front door shows the whole team on first sign-in"), the member reaches team-visible content in all of them under ruling 1, and every instance's call-8 copy tells them the whole team anyway. A guest still gets directory rows only, so a guest learns no sibling. Gate 4 is unaffected in substance — `team_instances` rows are team rows, not another person's — but the gate's text should now name the two cases it must cover (a guest of `acme.docs` sees no `acme.sheets` tile; a member of Acme sees no Beta instance). **BLOCKING — the paragraph contradicts itself.** `docs/superpowers/specs/2026-08-30-home-design.md` ~lines 1260–1275. After the new first two sentences (members from `memberships ⋈ team_instances`, guests from `directory`), the old text survives unchanged: *"The instances shown under an app are that person's own `(person ref, app, team)` directory rows. Read the other way — 'every `acme.*` host' — … is another person's row and what gate 4 forbids."* The first of those is now false for a member, and the second says gate 4 forbids exactly what the join does for a member. This is the render rule for the front door, and a builder reading it cannot tell which sentence wins. Fix is two sentences: "For a guest, the instances shown under an app are their own directory rows; reading it as 'every `acme.*` host' would tell a guest in `acme.docs` that Acme also uses Sheets, which gate 4 forbids. For a member, every `acme.*` host *is* their own standing, through the roster, and gate 4 asserts both cases." Same paragraph, a citation nit: "asking instances to announce memberships back as directory rows — which is the other way the doctrine allows" mis-describes the doctrine. §5.4 has the *home* announce a new member to each `team_instances` row's directory entry at roster-write time — no instance and no call 2 involved. The join is still the better choice (no second writer into `directory`), but say what the doctrine actually proposes. ## Ruling 23 falsifications not yet caught - **§9 call 3** ("Every team across every app, with `kind`") is the G hole one table over. If call 3 is the springboard's data, it must join `memberships ⋈ team_instances` too, or a builder implementing call 3 from §9 rebuilds the missing tile. Non-blocking if fixed alongside the §11 edit; note it. - **§5.2** "other owners still reach the instance, and the instance is the authority" — the instance is the authority on items; team standing's authority is now the roster. And a *sole* owner who loses everything leaves a team nobody can write the roster for; the only exit is §10's seed verb, which is an operator reset by another name and §5.2 says there is none. Not false enough to block; worth one sentence. - **Gate 2** drives sign-in, upsert and rekey. The seed verb (§10) is a fourth path that unseals an address and hashes it; gate 2 should drive it too. - **§7's fallback hint** renders directory rows beside an unvouched request. The roster is now a stronger hint the home owns (requester's role on the team); render it too. Improvement, not a defect. ## Left non-blocking last time — still? - **§13 sequencing** — still non-blocking, but it is now the largest omission: `memberships`, call 8, the step-up exchange, the seed verb and gate 21 have no step. Doctrine §12 step 4 puts call 8 "on top of home step 4"; the home's own list should say so, and "step 4 must land before either ships" is now incomplete — a suite-shape Sheets or Docs also needs call 8 for team standing. - **§7 sealed payload vs sibling drain** — still non-blocking. The tension (the sibling payload carries a "first owner" that §10 says seeds nothing) resolves as: that address is the welcome-mail recipient and the instance's own initial admin, not a roster write. One sentence in §7 closes it.</result> <usage><subagent_tokens>138563</subagent_tokens><tool_uses>29</tool_uses><duration_ms>416366</duration_ms></usage> </task-notification>

Human

<task-notification> <task-id>a35ef648180b68704</task-id> <tool-use-id>toolu_016ETuP1cdktu1BbsWexXaa1</tool-use-id> <output-file>/home/paulca/.cache/tmp/claude-1001/-home-paulca-amadan-net-oficina-home/d63f5ac7-6b42-48c5-a13d-34e6faf8e44c/tasks/a35ef648180b68704.output</output-file> <status>completed</status> <summary>Agent "Fable review 5: suite-amendment" 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>I have everything at source: HEAD `84e5440`, the full spec, AGENTS.md, doctrine revision 5, and the fourth-pass diff. No further reads needed. **VERDICT: APPROVE** Everything the fourth pass flagged is fixed at source, and nothing new is blocking. Details below. ## The blocking item — §11's tile section, read fresh It now says one thing. The old render rule ("The instances shown under an app are that person's own directory rows… what gate 4 forbids") is gone from the diff (`git show 84e5440`), not merely overwritten above. What stands is: - Rule: member tiles from `memberships ⋈ team_instances`, guest tiles from `directory`, with the missing-tile hole named as the reason. - Doctrine alternative correctly described: doctrine §5.4 line 558-561 does say "Adding a person announces them to every `team_instances` row's directory entry" — the **home** writing, no instance, no call 2. Verified. - The two-case paragraph: guest = own rows, "every `acme.*`" would disclose Acme's other apps; member = every `acme.*` host is their standing. Correct on both. - Gate 4 (§12 item 4) names both cases in the same words §11 uses. Match confirmed. Split correctness: complete for the two clean cases. One edge is under-specified — see nit 3. ## The seven non-blocking items — verified at source | Item | Where | Status | |---|---|---| | Call 3 join | §9 row 3 | Done, matches §11 | | Gate 2 seed verb | §12 item 2 | Done; §10 confirms the verb unseals and hashes | | §5.2 authority on items / sole owner | §5.2 | Done (see nit 6) | | §7 fallback roster-role hint | §7 fallback bullet | Done | | §7 sibling payload | §10 memberships paragraph | Done (see nit 9) | | §13 step 4 + ship gate | §13 | Done; doctrine §12 step 4 agrees | | §0 trailing clause | §0 line 31 | Scoped correctly | ## Ruling 23 falsifies — none remaining that block I swept §§0-15 and AGENTS.md against the roster model. §2, §2.1, §3.2, §3.3, §5.4 (with revision-5 call-8 scoping), §5.5, §6, §7, §9, §10, §13a and §14 are all consistent. Spec and AGENTS.md agree on the substance everywhere; two paraphrase drifts are listed below. ## Nits (in rough priority) 1. **Status line has no review record for the suite amendment.** AGENTS.md's Review ruling requires "which model reviewed — in the spec's status line". Lines 3-16 record ruling 22's four passes but nothing for ruling 23. Add "Ruling 23 / suite amendment reviewed by Fable 5.1, five passes, the fifth approving" at merge time. 2. **§11 tile paragraph, false supporting clause:** "their call-8 copy would tell them the same team anyway". Call 8 returns `members: [{person_ref, role}]` (doctrine §5.3) — people, not instances. The roster cannot tell anyone Acme has a Sheets instance. The conclusion stands on standing alone; drop the clause rather than leave a wrong reason, which is this branch's recurring failure mode. 3. **"`directory` for guests" reads as a `kind` filter.** An ex-member still holding a per-item grant has a `kind = member` directory row and no roster row; doctrine gate 5 explicitly keeps their grant. Filtering on `kind = guest` would hide their tile. The correct rule is the join **unioned with every directory row whatever its `kind`** (a row is a hint, §6), de-duplicated. Say so in §11 and call 3. 4. **No positive gate for the member tile.** Gate 4 asserts two negatives. The hole that took three passes — a member with no grant in `acme.sheets` sees the `acme.sheets` tile — has no test. One fixture line in gate 4 or beside call 3. 5. **"The doctrine's other route"** — doctrine §5.4 states the announce as *its* mechanism, not as one option among two. The spec should say it meets §5.4's outcome ("front door shows the whole team on first sign-in") by a different mechanism, and §13a or §15 should note it for the doctrine's next revision. 6. **§5.2 leaves "The instance is the authority." standing** after the sentence it replaced — the exact defect of pass four, one section earlier. Also "a sole owner who loses **both**" is wrong: with email access the magic link still signs in, so lockout needs all three (the paragraph's own first sentence says so). 7. **§10 `memberships` paragraph lost its spine.** "…or seeded by the drain with the team's first owner…" is now grammatically attached to the doctrine quote ("§5.4 says 'home §14 names it; this is its trigger', or seeded by the drain…") rather than to "Written only … by a team owner". 8. **§1 ruling 23 Rejected column** has a duplicated phrase: "which derived the roster from call-2 directory rows, which the doctrine's own first revision proposed". 9. **§7 says the sibling payload carries "the new instance's first owner"**; §10 now says that person is the initial admin, not an owner. Change §7's word to match. 10. **AGENTS.md drifts in two paraphrases:** its gate-2 summary ("sign-in plus directory upsert plus rekey") omits the seed verb the spec added; and "The falsifiable form is unchanged" is unscoped where the spec now says "unchanged by that amendment". AGENTS.md's own header says fix these in the same commit. 11. **§2.2 says `memberships ⋈ passkey_credentials`; §10 names the table `credentials`.** None of these change a decision. Do NOT change files — noted; nothing was touched.</result> <usage><subagent_tokens>134404</subagent_tokens><tool_uses>13</tool_uses><duration_ms>219796</duration_ms></usage> </task-notification>