oficina / home Public

Third pass: ruling 15's ground was wrong a third time, and a member had no tile

Ruling 15 has now failed three grounds and this one is mechanical and
checkable. It is not "per-team enrolment state slice one lacks":
enrolment is per person at one RP under ruling 1, and per-team
compliance is just memberships joined to passkey_credentials, both of
which now exist. What is missing is the DEMAND. passkey.Gate asks for
the ceremony only when the account is already enrolled — a count over
passkey_credentials — so an un-enrolled member of a floored team returns
(false, nil) and sails straight through the floor. A real floor needs a
per-team policy record AND a policy-driven demand at assertion minting:
step up if the session's Method lacks the factor, force enrolment if
there is none. That is the objection §5.2 and §14 now carry, and the
2FA-floor example is withdrawn from §7's argument entirely, since ruling
15's own Rejected column was buildability and it never illustrated §2.2
cleanly.

§2's bound was still wrong, in the other direction this time. "For as
long as the compromise lasts" is a floor, not a ceiling: a forged call-8
response outlives the compromise by up to one roster TTL in each
instance's copy, and a memberships row it actually wrote outlives it
until an owner finds and removes it — which is the "until an owner
notices" clause a previous pass deleted as contradicted. §13a was
misread both times: "what honest code leaves behind" means a row is not
evidence a compromise is obliged to leave, not that a written row does
not persist.

A pure member had no springboard tile. Tiles come from the viewer's own
directory rows, and call 2 fires when a grant changes — so a member with
team standing and no per-item grant has no directory row for that team,
and their own team was invisible on their front door. §11 now joins
memberships to team_instances for members and keeps directory rows for
guests. The home owns both tables, so it joins them rather than having
instances announce memberships back as directory rows, which would have
made call 2 a second roster writer.

§2.1's "existing sessions keep working" was false past the roster TTL:
team standing stops for people already in a session, so a long outage
degrades a signed-in member to their per-item grants. §2's "losing it
costs a convenience, not access" gains the roster as a second exception
beside sign-in.

Calls 2 and 5 now carry the roster epoch. The doctrine carries it in
calls 2, 5, 8 and the assertion; §9 had two of four, so a call-2 builder
reading only this spec would have shipped half the staleness carriers.

Three "the falsifiable form is unchanged" claims scoped to the amendment
that did not change it, since ruling 23 did. AGENTS.md's "sharing is a
grant on the instance — member or guest — never a copy" corrected: that
is the §5.5 status quo ruling 23 overturned, surviving in the binding
file.

Plus gate 21, because the most security-load-bearing new write in the
spec had no home-side gate — numbered 21 rather than 11 since
design-activity-pull owns 11-20.

Reviewed by Fable 5.1; three passes, all CHANGES NEEDED.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PsPrUY5B4wtAZHGZJ3PJyS
Paul Campbell pushed by paul@keymail.dev b0966ea42fde491aa21d1f113a7cc1e5881cb534 parent 3de8f0b
2 files changed, +62 −29
  • AGENTS.md +4 −2
  • docs/superpowers/specs/2026-08-30-home-design.md +58 −27

CI passed — run details

Intent

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>

Attributed to this commit: 75.7 g CO₂e (9786979 tokens). This is an attribution, not a measurement — usage counted from this commit's own marker forward to the next one on suite-amendment, 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/AGENTS.md b/AGENTS.md
index fb1ca99..78a9195 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -98,8 +98,10 @@ improvement.
instances across apps are one trust domain (spec ruling 23, suite ruling
1). This replaced *"never federate — data does not cross an instance
boundary"* on 2026-09-05: the boundary was drawn in the wrong place, since
- `acme.docs` and `acme.sheets` are the same team and always were. Sharing is
- a grant on the instance — member or guest — never a copy. §6 keeps the
+ `acme.docs` and `acme.sheets` are the same team and always were. **Sharing across teams** is a guest grant on the
+ instance, never a copy. **Team standing is the roster**, not a grant and
+ not a directory row (spec §5.5, §6: a directory row never grants team
+ standing whatever its `kind` says). §6 keeps the
two apart deliberately: "a row implying an org membership someone does
not have is a lie the directory tells on the org's behalf".
4. **Never become a support console.** Reading someone's data to help them
diff --git a/docs/superpowers/specs/2026-08-30-home-design.md b/docs/superpowers/specs/2026-08-30-home-design.md
index 8fb9cc6..8c69103 100644
--- a/docs/superpowers/specs/2026-08-30-home-design.md
+++ b/docs/superpowers/specs/2026-08-30-home-design.md
@@ -28,7 +28,7 @@ documents.
**"Retains", amended from "learns" on 2026-09-05** (ruling 18,
`design-activity-pull`, approved that day). The falsifiable form below is
-**unchanged** — nothing new goes at rest.
+**unchanged by that amendment** — ruling 23 later did change it, and says so — nothing new goes at rest.
**"Retains" means holds without a bound.** That definition is doing real
work and a first draft of this gloss got it wrong three times over, so it is
@@ -50,7 +50,8 @@ stated once, plainly:
maximum over ten thousand people is exactly the in-process title index this
gloss exists to forbid. At rest is the case **where the bound is not the
home's own to enforce** — a file outlives the process, and Litestream ships it — which is why
- the falsifiable form below is the test that matters and is unchanged. **Two
+ the falsifiable form below is the test that matters. (It is unchanged by
+ *this* amendment; ruling 23 changed it, and §0 says how.) **Two
platform facts sit behind that line and are not yet established** — whether
swap is configured and whether core dumps are off (§15). A swapped or
dumped page of titles is at rest, so the boundary is only as good as those
@@ -172,10 +173,14 @@ aspirational:
annotated.** A compromised **directory origin** can write the roster, and a
member reads everything team-visible in every instance of that team — so it
costs the graph, a convenience outage, **and team-visible content across a
- team's apps for as long as the compromise lasts.** Not "until an owner
- notices": §13a is explicit that a directory row and a notification are
- "what honest code leaves behind", and the owner's only remedy runs back
- through the compromised origin. It still costs no *per-item* grant, which
+ team's apps for the duration of the compromise — **plus one roster TTL for
+ any forged call-8 response, plus, for any `memberships` row it actually
+ wrote, until an owner finds and removes it.** The bound is a floor, not a
+ ceiling. An earlier pass of this branch wrote "until an owner notices" and
+ a later one deleted it as contradicted by §13a; both were wrong. §13a's
+ "what honest code leaves behind" means a row is not evidence a compromise
+ is *obliged* to leave — not that a written row does not persist. A written
+ row persists exactly until somebody removes it. It still costs no *per-item* grant, which
is the thing the home does not hold. **The credential origin is a
different and worse tier** — §13a: it "can sign in as anyone", and so reads
individually granted items too — and this bullet is about the directory,
@@ -189,8 +194,10 @@ aspirational:
hole §3.1's live-host model already concedes — it is, and it is bigger than
it was, so it is said here rather than left to the other document.
- **Losing it costs a convenience, not access** — for the directory. Sign-in
- is the exception and §5 says so rather than pretending otherwise; §2.1
- lists what shrinks it.
+ is one exception and §5 says so rather than pretending otherwise; §2.1
+ lists what shrinks it. **The roster is a second, added by ruling 23**: past
+ its TTL, team-rung access stops for everyone, and §10 records that it does
+ not rebuild itself.
- **It stays small.** Every proposed feature is weighed against "does this
belong in an instance instead?" A home that grows features grows blast
radius.
@@ -198,7 +205,7 @@ aspirational:
### 2.1 The single point of failure, and cheap mitigations
Sign-in is an authority, so a home outage stops *new* sessions suite-wide.
-Existing sessions keep working. Four things shrink that exception without
+Existing sessions keep working **for individual grants**. Since ruling 23 they do **not** keep team standing: past the roster TTL with no successful refetch an instance stops admitting anyone on team standing, people already in a session included (doctrine §5.3). So a long home outage degrades a signed-in member to their per-item grants. Four things shrink that exception without
changing the architecture:
- **The home does not hibernate.** It is the one app on the estate where
@@ -254,13 +261,18 @@ Each is something somebody will propose in good faith.
*permits*. An instance demands nothing at authentication; it exchanges an
assertion.
- **So ruling 15 stands on mechanics, not on §2.2.** `passkey.Gate` keys on
- `Enrolled(sess.Subject)` — per account, with no team in scope — and
- building a per-team floor means a per-team policy record and a per-team
- enrolment state that slice one does not have. That is the objection, and
- it is the one §14's row should carry. **§5.2's "a home-side policy
- contradicts §2.2" and §14's "argued past §2.2" are now false and are
- corrected there.**
+ **So ruling 15 stands on mechanics, not on §2.2 — and the mechanics are
+ not "per-team enrolment", which was a third wrong ground.** Enrolment is
+ per person at one RP (ruling 1), and per-team *compliance* is just
+ `memberships ⋈ passkey_credentials`, both of which now exist. What is
+ actually missing is the demand: **`passkey.Gate` asks for the ceremony
+ only when the account is already enrolled** — `Enrolled(sess.Subject)`, a
+ count over `passkey_credentials` — so an un-enrolled member of a floored
+ team returns `(false, nil)` and sails straight through the floor. A real
+ floor therefore needs two things slice one has neither of: **a per-team
+ policy record**, and **a policy-driven demand at assertion minting** —
+ step up if the session's `Method` lacks the factor, force enrolment if
+ there is none. That is the objection, and §5.2 and §14 carry it.
**It does hold the membership roster** (suite ruling 4, ruling 23). "Is
this person on this team" is a fact about the home's own records. "May
@@ -506,9 +518,9 @@ the suite entirely. No passwords.
Passkey via `passkey.Handlers.Gate`, **optional per person**.
**Per-team enforcement is not in slice one** (ruling 15). The first draft
-promised it and neither spec could build it: no call carried a team's
-policy, `Gate` is strictly per-account (`Enrolled(sess.Subject)`), and every
-workaround breached something — a home-side per-team policy needs a per-team policy record and per-team enrolment state slice one does not have (ruling 15, re-grounded 2026-09-05 — it was §2.2 until ruling 23 rewrote that clause), and an
+promised it and neither spec could build it: there was no per-team policy record, `Gate` is strictly per-account
+(`Enrolled(sess.Subject)`) and asks nothing of an un-enrolled account, and
+every workaround breached something — an
`amr` claim on the assertion fails §5.4's boundary test. It moves to §14
with a trigger, rather than being discovered by the first paying team that
flips the switch.
@@ -780,8 +792,11 @@ argued this away as "a name, not a document". **That argument is wrong and
is recorded here so it is not made again:** §2.2 says *never holds an
authorization model* — the clause as it stood before ruling 23 rewrote it —
not *never holds a content authorization model*, and
-ruling 15 rejected a per-team 2FA floor — a policy with no content in it at
-all — on exactly these grounds. So the distinction has to be sharper. Two
+ruling 15 rejected a per-team 2FA floor. **(That example is withdrawn from
+this argument.** Ruling 15's own Rejected column was buildability, and §2.2
+now grounds it on mechanics — an un-enrolled member sails through `Gate`,
+and there is no per-team policy record. It was never a clean illustration of
+§2.2 and it is not one now.) So the distinction has to be sharper. Two
paths, and the home answers only facts about its own records in both:
**Both paths end in the queue**, and this is not a detail. §7's
@@ -902,10 +917,10 @@ every assertion, and one that fails §5.4 is not a preference.
| # | Call | Direction | Notes |
|---|---|---|---|
| 1 | Sign-in and session minting | person → home | The identity authority. Not a hint. |
-| 2 | Directory upsert / revoke | instance → home | Sends address **+ sealed team name, which is advisory**: the home owns the display name (§10) and does not overwrite it, so this is a consistency check the home may log a divergence on, never a write. Home hashes; **returns the person ref** and the pepper epoch. Instance-scoped revoke included. Via the outbox. |
+| 2 | Directory upsert / revoke | instance → home | Sends address **+ sealed team name, which is advisory**: the home owns the display name (§10) and does not overwrite it, so this is a consistency check the home may log a divergence on, never a write. Home hashes; **returns the person ref**, the pepper epoch **and the roster epoch** (ruling 23 — the doctrine carries it in calls 2, 5 and 8 and in the assertion; a builder reading only §9 would otherwise ship two of the four staleness carriers). Instance-scoped revoke included. Via the outbox. |
| 3 | Directory list | person → home | Every team across every app, with `kind`. |
| 4 | Provisioning enqueue | person → home | Rate-limited, depth-capped, human-drained. |
-| 5 | Rekey journal pull | instance → home | **Scoped to this consumer**, keyed on person refs. |
+| 5 | Rekey journal pull | instance → home | **Scoped to this consumer**, keyed on person refs. Carries the **roster epoch** (ruling 23). |
| 6 | Preference write-through | person → home | Redirect POST to the directory origin (§8). |
| 7 | Activity pull | home → instance | **An assertion for the person**, `aud` = the instance. The home renders and discards; RAM only, never at rest. `design-activity-pull` §4–5, suite §6. |
| 8 | Roster pull | instance → home | Consumer token, scoped to the caller's own team. Returns `(generation, epoch)` and the team's members with roles, **paged with an epoch-bound cursor**, the **epoch per team** (a suite-wide epoch would make a bump in one team a cross-team activity signal to every instance, which suite ruling 1 forbids), and a request for a team that is not the caller's answered **identically to an unknown team — 404, never 403** (gate 4's pattern): a page from a superseded epoch is refused, and an instance stages every page and replaces its roster atomically only when a complete set for one epoch has arrived. Suite §5.3. |
@@ -1240,7 +1255,16 @@ a positive `max-age`, keyed on host, path and query, shared across every
visitor, and it does **not** refuse on a request `Cookie` — which is why
this needs a gate rather than a convention.
-**Tiles come from the viewer's own rows, never from the label.** The
+**Tiles come from the viewer's own rows, never from the label** — and since
+ruling 23 "rows" means two tables, not one. **A member's tiles come from
+`memberships ⋈ team_instances`**; a **guest's** come from their `directory`
+rows, as before. This is not a refinement, it is a hole that had to be
+closed: a member with team standing and no per-item grant has **no directory
+row at all** (call 2 fires when a grant changes), so a tiles-from-directory
+front door would have hidden their own team from them. The home owns both
+tables, so it joins them rather than asking instances to announce
+memberships back as directory rows — which is the other way the doctrine
+allows, and the one that would have made call 2 a second roster writer. The
instances shown under an app are that person's own `(person ref, app, team)`
directory rows. Read the other way — "every `acme.*` host" — a guest in
`acme.docs` would learn that Acme also uses Sheets, which is another
@@ -1314,6 +1338,13 @@ No template gallery, no upsell chrome, no waffle.
edge refuses on `Authorization` is not something to rely on), the
personalised `/` redirect included (ruling 22). It exists for the edge-cache
reason §11 sets out.
+21. **The roster write** — assert it requires a fresh assertion (§14's
+ step-up, triggered by ruling 23), that call 2 cannot write `memberships`,
+ and that a write naming a team the caller does not own answers 404 like
+ any other unknown team. The most security-load-bearing new write in this
+ spec had no home-side gate; suite gates 5 and 8 are instance-side and
+ schema-side.
+ (Numbered 21, not 11: `design-activity-pull` owns gates 11-20.)
Review on a deployed canary, never localhost. Verify with
`X-Carlos-Version` on the canonical host after the watch.
@@ -1356,7 +1387,7 @@ ships**, because call 2's person ref is what their grant rows key on.
## 13a. Consumed suite rulings
The suite sharing doctrine (`oficina/suite`) binds this spec. Read against
-**revision 4, commit `210312f`**. Ruling 23 adopts it.
+**revision 4, commit `210312f` — with §5.4 here consuming a revision-5 rule (the epoch exception's call-8 scoping), which gate 9's one-revision grace allows and which is named rather than glossed**. Ruling 23 adopts it.
| # | What it settles for the home |
|---|---|
@@ -1409,7 +1440,7 @@ identity authority. That filing belongs at `carlos/pegamento`.
| Thing | Trigger |
|---|---|
-| **Owner-enforceable per-team 2FA** (ruling 15) | A team asks, *and* either a call carrying team policy is built — `passkey.Gate` keys on `Enrolled(sess.Subject)`, per account, so this needs a per-team policy record and per-team enrolment state — or rastrillo grows a per-team notion. **Not "argued past §2.2"**: ruling 23 rewrote that clause, and the objection is mechanical rather than doctrinal |
+| **Owner-enforceable per-team 2FA** (ruling 15) | A team asks, *and* **a per-team policy record is written** (an owner's write at the directory origin, the roster's own shape) **and assertion minting learns to act on it** — `passkey.Gate` asks for the ceremony only when the account is already enrolled, so an un-enrolled member of a floored team passes straight through and a floor has to force enrolment, not merely demand a ceremony. **Not "argued past §2.2"**, and not "a sixth call carrying team policy": ruling 23 rewrote that clause, and since the home would enforce at minting no call carries policy anywhere |
| Pegamento's vault facet — team names the home cannot read | The vault ships with a recovery story |
| Operator reset for a fully locked-out person | The first person loses passkey and codes together |
| Auto-drain of the queue | The platform ships a machine credential, **and** we choose to give up the abuse gate, **and** ruling 19's fallback adjudication is automated — three decisions. A machine credential supplies no judgement about who may take a reserved name (§7) |
@@ -1425,7 +1456,7 @@ identity authority. That filing belongs at `carlos/pegamento`.
## 15. Open
-- **Docs' re-review** of §9 after this revision — the contract changed
+- **Docs' re-review** of §9 after this revision — **calls 7 and 8 included** — the contract changed
substantially (person refs, call 5 rewritten, call 6 added).
- **`rastrillo/rastrillo` #2** — the `auth` subject hook and sealed link
store. Adopt and delete ours the day it lands.