R3 runner, area 1 of 4. Part of the #9296 wave. No clause failed. Ten items were not reached and are recorded not-run rather than inferred — see Coverage honesty.
⚠️Title deviates from the wave's (FULL area) convention on purpose: this run consulted 9 of 20 items, so claiming a full area would be false. Scope is stated per item below.
Environment fingerprint
| |
|---|
| framework sha (subject) | e4e5c6e3c608b1b807c83a0d5b734f213eb1a1dd — unmoved all run |
.objectui-sha (pin) | 665661ab093263f39f2e660a295ea615dbcee35a |
| console dist stamp | 665661ab0932 — pnpm check:console-sha ✓ "Console dist matches the objectui pin" |
| ports | 3300 (smoke), 3301, 3302, 3303 — band 3300–3399 |
| dbs | file:/tmp/qa-r3b/<area>-<item>.db, rm -f before each boot |
| seed | --seed-admin → admin@objectos.ai; members via real POST /auth/sign-up/email |
| boot smoke | /api/v1/health 200 in 2.2s; /_console/ 200, text/html, app shell present |
| date | 2026-08-17 |
Origin note: better-auth refuses 127.0.0.1 with 403 INVALID_ORIGIN. All driving used http://localhost:<port>.
⚠️ Spec provenance — read this before trusting the revision column
The brief said to take the checklist forward from origin/main once PR #9309 merged. #9309 has not merged — it is open, CI-green, and explicitly held ("no auto-merge, not enqueued", pending the maintainer). Waiting would have stalled all four areas indefinitely.
So the spec was taken from PR #9309 head fbd1c0491215406c379405cef262befad1de458d, not from merged main. Validated with the pinned validator (#9309 is docs-only, so scripts/ is the subject's own):
check-platform-checklist: OK — 15 areas, 190 items (190 active); coverage: 30 kinds mapped, 0 waived.
✓ checklist-select self-test: 17 cases pass.
git status showed 8 modified paths, all under docs/qa/platform-checklist/, 0 elsewhere. Never committed. If #9309 changes before merge, re-check the revisions cited here.
Selector: node scripts/checklist-select.mjs area:access-security --json → 19 runnable + 1 hidden-blocked = 20, matching the post-sweep ledger.
Setup finding — the wave's documented recipe does not reach a bootable server on a cold tree
Following the brief exactly (pnpm install → client prebuild → pnpm objectui:build → boot) the smoke failed:
› Error: command dev not found
packages/cli had no dist at all — the @objectstack/client... filter does not reach it, and objectui:build builds the console, not the framework CLI. Fixed with pnpm exec turbo run build --filter=@objectstack/cli... --filter=@objectstack/example-showcase... (60 tasks, 1m19s). Not a product defect — the recipe assumed a tree R0 had already fully built — but the setup steps as written are incomplete for a fresh container. Worth folding into the R0 card.
The #9307 workaround itself behaved exactly as documented: client prebuild 32 tasks / 6m08s, then objectui:build succeeded first try.
Automated pins (RUNNER rule 6)
One vitest invocation over the area's 12 pinned files — 12 files / 91 tests / 91 passed / 0 failed, 79.56s. Re-run with --reporter=json for per-test citation (/tmp/qa-r3b/access-security-pins.json). Per-file: anonymous-deny-surfaces 25 · permission-zoo 13 · owner-anchor-and-bulk-writes 9 · scope-depth-write 9 · scope-depth 7 · private-owd 5 · readonly-when-parent 5 · controlled-by-parent 4 · invoice-cbp 4 · scope-depth-fallback 4 · public-read-owd 3 · static-readonly 3.
objectstack verify --rls --json (the packages/verify pin), stock showcase, multiTenant: false:
rls : {objects:23, consistent:20, holes:0, memberVisible:0, probeBlocked:0, skipped:3, proven:20, unproven:3}
crud : {objects:23, verified:15, fidelityGaps:0, createFailed:0, needsFixture:1, readFailed:0, skipped:7, mismatchTotal:0}
hardFailures: 0
positionCoverage: declared 9 / ran 9 / notRun []
Per-clause verdicts
access-security.rls-both-sides — rev 3, P0, api → partial
| clause | verdict | oracle evidence (text) |
|---|
| A0 list isolation both ways | pass | A list 200 ["A note 1","A note 2"]; B list 200 ["B note 1"]; each excludes the other |
| A1 foreign by-id READ denied | pass | GET /data/showcase_private_note/<B id> as A → 404 RECORD_NOT_FOUND (not-found-shaped denial, which the clause admits alongside 403) |
| A2 foreign by-id WRITE denied + row unchanged | pass | PATCH as A → 403 PERMISSION_DENIED; admin re-read body"b-original" before and after |
| A3 admin reads full set | pass | admin list 200, n=3, contains both A's and B's ids |
A4 runRlsProofs holes = 0 | pass | summary.holes: 0 over 23 showcase objects, 20 proven consistent |
| A5 skips explainable + D11-judged spot-check | blocked(dependency) | explainability half proven — the 3 skips each name a benign reason (below). The spot-check half is judged against a write-up delivered privately under the #7463 ruling, which this runner does not hold. Per the clause's own instruction it is not ticked green. |
| N0 no silent foreign write | pass | forged PATCH 403, rowMutated=false |
The 3 skipped rows, each with a named benign reason: showcase_project_membership — "no plain-text probe field"; showcase_ext_customer / showcase_ext_order — "external read-only object (federated datasource "showcase_external"; no inserts)".
access-security.write-path-guards — rev 2, P0, api → partial
| clause | verdict | oracle evidence (text) |
|---|
| A0 readonly stripped on INSERT, create still succeeds | pass | POST 201 with X-ObjectStack-Dropped-Fields: lead_score;reason=readonly; readback lead_score absent (payload sent 99) — admit-and-strip, #3431 advertised |
| A1 readonly stripped on UPDATE | pass | PATCH 200, same dropped header; lead_score unchanged; sibling editable namedid land |
| A2 owner forge on INSERT denied, no row | pass | 403 "'owner_id' … is system-managed — creating a record owned by another user requires the transfer grant"; admin-side planted count 0 |
| A3 transfer + disown denied, self-owner insert OK | pass | both PATCHes 403; owner re-reads as A; POST owner_id=<self> → 201, readback owner = A |
| A4 empty-owner bulk insert stamps caller | pass | POST /data/showcase_private_note/batch {operation:'create',records:[…]} → 200, succeeded:2; both rows read back with the single owner = A's id |
A5 readonlyWhen locks on paid invoice + lines | pass (pin) | showcase-readonly-when-parent 5/5 incl. "a paid invoice's frozen line cannot be rewritten over the API", "leaves an unlocked field on the same frozen line writable" |
| A6 bulk update validates per row (#3106) | not-run | no mixed-batch probe driven |
| N0 same ops as admin succeed | pass | admin POST owner_id=<B> → 201, readback owner = B — guard gates on privilege, not on the key |
N1 isSystem may still set readonly | not-run | needs an objectql write with context:{isSystem:true} |
showcase_contact confirmed from live metadata: requires name+email; lead_score is readonly:true with no defaultValue — matching the fixture.
access-security.anonymous-deny-surfaces — rev 1, P0, api → pass
| clause | verdict | oracle evidence (text) |
|---|
A0 every surface 401 UNAUTHENTICATED | pass (pin) | 25/25; /meta, /data, /actions, /automation (trigger, list, delete), /packages (list, export, discard-drafts, publish-drafts) |
| A1 deny precedes resource resolution | pass (pin) | anonymous action probe 401; and the member-side contrast is pinned separately (#7867: member + nonexistent id → 404 RECORD_NOT_FOUND, same as the data surface) |
| A2 exactly two envelope families, no third dialect | pass (pin) | 6 per-surface assertions each naming its single declared family — rest-flat for /meta/data, dispatcher-wrapper for /actions/automation; plus "ANONYMOUS_DENY_BODY is the REST seam body, and NOT the dispatcher one (#5632)" |
| A3 authenticated member unaffected | pass (pin) | member allowed on /meta, data surface, action surface |
| A4 declared-public routes stay public | pass (live) | anonymous GET /forms/<slug>, GET /forms/<slug>/lookup/<f>, POST /forms/<slug>/submit → 404 FORM_NOT_FOUND (the handler's own error — auth did not intercept); same-server contrast anonymous GET /data/showcase_private_note → 401 UNAUTHENTICATED |
| A5 every variant probed | pass (pin) | all listed families driven; none left unprobed |
| N0 destructive automation denied, flow survives | pass (pin) | "anonymous DELETE /automation/:name is denied (401) — the destructive one" + authed caller reaches the domain (501, not 401) |
access-security.crud-permission-matrix — rev 1, P0, api → partial
Engine-level halves proven by the pins: runCrudVerification 23 objects / 15 verified / 0 fidelityGaps / 0 mismatchTotal; runRlsProofs0 holes; positionCoverage all 9 declared positions ran (notRun: []); permission-zoo pins the VAMA read bypass, the read-only auditor's write refusal, and delegated-admin subtree limits (13/13).
The 31-row × 4-verb per-cell matrix from access-matrix.json was not driven cell-by-cell — A6 explicitly makes an undriven cell set at best partial, so that is the verdict. A0–A5 are supported but not per-cell proven; not ticked.
access-security.owd-sharing-matrix — rev 2, P0, api → partial
| model | verdict | evidence |
|---|
private (A0) | pass (pin) | private-owd 5/5 — owner-only list, foreign by-id read refused, foreign write refused, symmetric for the other member |
public_read (A1, A2) | pass (pin) | public-read-owd 3/3 — every member reads a foreign announcement, only the owner edits; explicit contrast with private |
controlled_by_parent (A4, A5) | pass (pin) | controlled-by-parent 4/4 + invoice-cbp 4/4 — derived read and by-id write refusal, plus "NOT over-blocked" for the owned master |
public_read_write (A3) | not-run | needs a live C1-vs-A PATCH pair on showcase_project |
| A6 all four models recorded | partial | three of four driven |
A4 carries the dedupe note pointing at rls-both-sides clause 5; that verdict is recorded there only, once (#7687).
access-security.scope-depth-asymmetry — rev 1, P1, api → partial
Pins (20 tests across 3 files) prove the depth semantics directly: read widening to BU co-members but not child/sibling BUs; subtree BFS descent; manager-chain BFS; the read/write asymmetry ("the co-member's note is READABLE (readScope 'unit')… yet NOT editable"); writeScope 'unit' widening a co-member edit; a child-BU member's note not editable; and three separate fail-closed degradations ("degrades to owner-only — no widening, never fail-open"). A5's explicit diff of recorded outcomes against access-matrix.json's readScope/writeScope columns was not performed → partial.
access-security.record-access-explain — rev 2, P1, mixed → partial
Pin covers the explain attribution case: "explain() reports the vama_bypass layer with the auditor set as contributor". Console-side clauses not driven.
access-security.fls-mask-and-strip — rev 1, P1, mixed → partial
Pin covers the write half only, as the item's own automated.ref states: "FLS: a contributor can edit a project but NOT its budget fields". Read-masking half not driven.
access-security.no-active-org-session-semantics — rev 1, P0, api → blocked(fixture)
New in the R4 sweep; never run before. The state under test is not reachable on the stock fixture, and the deployment shape that would make it reachable cannot be booted from this repo. Both halves measured, not assumed:
Stock posture is single, so the wall is inert.resolveTenancyPosture() returns single when OS_TENANCY_POSTURE/OS_MULTI_ORG_ENABLED are unset; postureEnforcesWall('single') === false; computeTenantLayer0Filter returns null before reaching any deny sentinel. objectstack verify independently reported multiTenant: false. The item itself says every clause is vacuous on an unwalled deployment. Rows do carry a real organization_id (e.g. org_msxay12mhknse86x) from the ADR-0081 default-org bootstrap, so a session with no active organization is not reachable this way either.
Provisioning the wall is refused at boot.OS_TENANCY_POSTURE=isolated on the showcase → server never listens (health=000), FATAL:
tenancy posture 'isolated' was requested but @objectstack/organizations could not be loaded, so the organization wall is INACTIVE. Refusing to boot … cause: Cannot find package '@objectstack/organizations': the host app does not declare it.
That refusal is the correct ADR-0093 D5 / ADR-0105 D12 behaviour ("you never silently get an unenforced organization boundary") and is recorded here as a positive observation, not a defect. Its consequence for this item is that the fixture is unprovisionable in the open repo: the offered escape OS_ALLOW_DEGRADED_TENANCY=1 boots explicitly degraded single-org, i.e. still no wall, so it does not supply the fixture either.
Checklist feedback (not a code defect): the item's knownGaps anticipate that producing the no-active-org state is awkward (racy signup vs. removed-member), but not that the walled deployment itself is unavailable on the stock showcase without an enterprise package. As written, the item is unrunnable here regardless of which producer a run picks. It needs either an @objectstack/organizations-bearing fixture or a blocked marker. Recommend a checklist edit (revise + bump revision + append history) rather than leaving future runners to re-derive this.
access-security.share-link-capability-tokens — rev 1, P2, api → blocked(fixture)
Carried blocked in the ledger; hidden by the resolver by design. Recorded, not run.
Not-run (10)
No oracle consulted — not inferred from siblings: readonly-package-locks-studio (P2) · sharing-rules-widen (P1) · record-share-grant-revoke (P1) · audit-log-browser (P1) · owd-save-gate (P1) · public-form-intake (P1) · suggested-binding-loop (P2) · sharing-rule-authoring-ui (P2) · permission-matrix-edit-loop (P1) · capability-declaration-lifecycle (P1).
Two notes for whoever picks these up. suggested-binding-loop's clause 0 is a known expected-fail on stock (#7677 — stock auto-binds the only isDefault suggestion at boot and surfaces no row at all); its knownGaps carry the unbind sequence that provisions a genuine pending row. And per the item's own N2, an empty list must never be ticked green.
Coverage honesty
9 of 20 items consulted; no clause failed; 1 item fully passed, 7 partial, 2 blocked, 10 not-run. The area's two-sided gate discipline held everywhere it was exercised: every refusal above was proven server-side on a forged direct request, with an admin re-read confirming non-mutation, and every guard was paired with the entitled-persona contrast that proves the route is not simply broken (wrong-persona). No verdict rests on UI absence, and no DOM oracle was used.
Fixture gaps found: the tenancy-wall gap above (P0 item unrunnable) and the pre-existing share-link-capability-tokens block. Neither is a product defect. #9308 remains the home for the seed-shaped second-login gaps — though note that stack.signUp() / POST /auth/sign-up/emaildoes yield real loginable second personas, which is what let every gate here be driven from both sides without one.
Links: #9296 (wave) · #9309 (spec provenance) · #9307 (console-build flake, worked around as documented) · #9308 (fixture card).
R3 runner, area 1 of 4. Part of the #9296 wave. No clause failed. Ten items were not reached and are recorded
not-runrather than inferred — see Coverage honesty.(FULL area)convention on purpose: this run consulted 9 of 20 items, so claiming a full area would be false. Scope is stated per item below.Environment fingerprint
e4e5c6e3c608b1b807c83a0d5b734f213eb1a1dd— unmoved all run.objectui-sha(pin)665661ab093263f39f2e660a295ea615dbcee35a665661ab0932—pnpm check:console-sha✓ "Console dist matches the objectui pin"file:/tmp/qa-r3b/<area>-<item>.db,rm -fbefore each boot--seed-admin→admin@objectos.ai; members via realPOST /auth/sign-up/email/api/v1/health200 in 2.2s;/_console/200,text/html, app shell presentOrigin note: better-auth refuses
127.0.0.1with403 INVALID_ORIGIN. All driving usedhttp://localhost:<port>.revisioncolumnThe brief said to take the checklist forward from
origin/mainonce PR #9309 merged. #9309 has not merged — it is open, CI-green, and explicitly held ("no auto-merge, not enqueued", pending the maintainer). Waiting would have stalled all four areas indefinitely.So the spec was taken from PR #9309 head
fbd1c0491215406c379405cef262befad1de458d, not from mergedmain. Validated with the pinned validator (#9309 is docs-only, soscripts/is the subject's own):git statusshowed 8 modified paths, all underdocs/qa/platform-checklist/, 0 elsewhere. Never committed. If #9309 changes before merge, re-check the revisions cited here.Selector:
node scripts/checklist-select.mjs area:access-security --json→ 19 runnable + 1 hidden-blocked = 20, matching the post-sweep ledger.Setup finding — the wave's documented recipe does not reach a bootable server on a cold tree
Following the brief exactly (
pnpm install→ client prebuild →pnpm objectui:build→ boot) the smoke failed:packages/clihad nodistat all — the@objectstack/client...filter does not reach it, andobjectui:buildbuilds the console, not the framework CLI. Fixed withpnpm exec turbo run build --filter=@objectstack/cli... --filter=@objectstack/example-showcase...(60 tasks, 1m19s). Not a product defect — the recipe assumed a tree R0 had already fully built — but the setup steps as written are incomplete for a fresh container. Worth folding into the R0 card.The #9307 workaround itself behaved exactly as documented: client prebuild 32 tasks / 6m08s, then
objectui:buildsucceeded first try.Automated pins (RUNNER rule 6)
One vitest invocation over the area's 12 pinned files — 12 files / 91 tests / 91 passed / 0 failed, 79.56s. Re-run with
--reporter=jsonfor per-test citation (/tmp/qa-r3b/access-security-pins.json). Per-file: anonymous-deny-surfaces 25 · permission-zoo 13 · owner-anchor-and-bulk-writes 9 · scope-depth-write 9 · scope-depth 7 · private-owd 5 · readonly-when-parent 5 · controlled-by-parent 4 · invoice-cbp 4 · scope-depth-fallback 4 · public-read-owd 3 · static-readonly 3.objectstack verify --rls --json(thepackages/verifypin), stock showcase,multiTenant: false:Per-clause verdicts
access-security.rls-both-sides— rev 3, P0, api → partial200 ["A note 1","A note 2"]; B list200 ["B note 1"]; each excludes the otherGET /data/showcase_private_note/<B id>as A → 404RECORD_NOT_FOUND(not-found-shaped denial, which the clause admits alongside 403)PERMISSION_DENIED; admin re-readbody"b-original"before and after200, n=3, contains both A's and B's idsrunRlsProofsholes = 0summary.holes: 0over 23 showcase objects, 20 proven consistentrowMutated=falseThe 3
skippedrows, each with a named benign reason:showcase_project_membership— "no plain-text probe field";showcase_ext_customer/showcase_ext_order— "external read-only object (federated datasource "showcase_external"; no inserts)".access-security.write-path-guards— rev 2, P0, api → partialX-ObjectStack-Dropped-Fields: lead_score;reason=readonly; readbacklead_scoreabsent (payload sent 99) — admit-and-strip, #3431 advertisedlead_scoreunchanged; sibling editablenamedid landPOST owner_id=<self>→ 201, readback owner = APOST /data/showcase_private_note/batch {operation:'create',records:[…]}→ 200,succeeded:2; both rows read back with the single owner = A's idreadonlyWhenlocks on paid invoice + linesshowcase-readonly-when-parent5/5 incl. "a paid invoice's frozen line cannot be rewritten over the API", "leaves an unlocked field on the same frozen line writable"POST owner_id=<B>→ 201, readback owner = B — guard gates on privilege, not on the keyisSystemmay still set readonlycontext:{isSystem:true}showcase_contactconfirmed from live metadata: requiresname+email;lead_scoreisreadonly:truewith nodefaultValue— matching the fixture.access-security.anonymous-deny-surfaces— rev 1, P0, api → passUNAUTHENTICATED/meta,/data,/actions,/automation(trigger, list, delete),/packages(list, export, discard-drafts, publish-drafts)#7867: member + nonexistent id → 404RECORD_NOT_FOUND, same as the data surface)/meta/data, dispatcher-wrapper for/actions/automation; plus "ANONYMOUS_DENY_BODYis the REST seam body, and NOT the dispatcher one (#5632)"/meta, data surface, action surfaceGET /forms/<slug>,GET /forms/<slug>/lookup/<f>,POST /forms/<slug>/submit→ 404FORM_NOT_FOUND(the handler's own error — auth did not intercept); same-server contrast anonymousGET /data/showcase_private_note→ 401UNAUTHENTICATEDaccess-security.crud-permission-matrix— rev 1, P0, api → partialEngine-level halves proven by the pins:
runCrudVerification23 objects / 15 verified / 0 fidelityGaps / 0 mismatchTotal;runRlsProofs0 holes;positionCoverageall 9 declared positions ran (notRun: []); permission-zoo pins the VAMA read bypass, the read-only auditor's write refusal, and delegated-admin subtree limits (13/13).The 31-row × 4-verb per-cell matrix from
access-matrix.jsonwas not driven cell-by-cell — A6 explicitly makes an undriven cell set at bestpartial, so that is the verdict. A0–A5 are supported but not per-cell proven; not ticked.access-security.owd-sharing-matrix— rev 2, P0, api → partialprivate(A0)public_read(A1, A2)controlled_by_parent(A4, A5)public_read_write(A3)showcase_projectA4 carries the dedupe note pointing at
rls-both-sidesclause 5; that verdict is recorded there only, once (#7687).access-security.scope-depth-asymmetry— rev 1, P1, api → partialPins (20 tests across 3 files) prove the depth semantics directly: read widening to BU co-members but not child/sibling BUs; subtree BFS descent; manager-chain BFS; the read/write asymmetry ("the co-member's note is READABLE (readScope 'unit')… yet NOT editable");
writeScope 'unit'widening a co-member edit; a child-BU member's note not editable; and three separate fail-closed degradations ("degrades to owner-only — no widening, never fail-open"). A5's explicit diff of recorded outcomes againstaccess-matrix.json'sreadScope/writeScopecolumns was not performed → partial.access-security.record-access-explain— rev 2, P1, mixed → partialPin covers the explain attribution case: "explain() reports the vama_bypass layer with the auditor set as contributor". Console-side clauses not driven.
access-security.fls-mask-and-strip— rev 1, P1, mixed → partialPin covers the write half only, as the item's own
automated.refstates: "FLS: a contributor can edit a project but NOT its budget fields". Read-masking half not driven.access-security.no-active-org-session-semantics— rev 1, P0, api → blocked(fixture)New in the R4 sweep; never run before. The state under test is not reachable on the stock fixture, and the deployment shape that would make it reachable cannot be booted from this repo. Both halves measured, not assumed:
Stock posture is
single, so the wall is inert.resolveTenancyPosture()returnssinglewhenOS_TENANCY_POSTURE/OS_MULTI_ORG_ENABLEDare unset;postureEnforcesWall('single') === false;computeTenantLayer0Filterreturnsnullbefore reaching any deny sentinel.objectstack verifyindependently reportedmultiTenant: false. The item itself says every clause is vacuous on an unwalled deployment. Rows do carry a realorganization_id(e.g.org_msxay12mhknse86x) from the ADR-0081 default-org bootstrap, so a session with no active organization is not reachable this way either.Provisioning the wall is refused at boot.
OS_TENANCY_POSTURE=isolatedon the showcase → server never listens (health=000), FATAL:That refusal is the correct ADR-0093 D5 / ADR-0105 D12 behaviour ("you never silently get an unenforced organization boundary") and is recorded here as a positive observation, not a defect. Its consequence for this item is that the fixture is unprovisionable in the open repo: the offered escape
OS_ALLOW_DEGRADED_TENANCY=1boots explicitly degraded single-org, i.e. still no wall, so it does not supply the fixture either.Checklist feedback (not a code defect): the item's
knownGapsanticipate that producing the no-active-org state is awkward (racy signup vs. removed-member), but not that the walled deployment itself is unavailable on the stock showcase without an enterprise package. As written, the item is unrunnable here regardless of which producer a run picks. It needs either an@objectstack/organizations-bearing fixture or ablockedmarker. Recommend a checklist edit (revise + bumprevision+ appendhistory) rather than leaving future runners to re-derive this.access-security.share-link-capability-tokens— rev 1, P2, api → blocked(fixture)Carried
blockedin the ledger; hidden by the resolver by design. Recorded, not run.Not-run (10)
No oracle consulted — not inferred from siblings:
readonly-package-locks-studio(P2) ·sharing-rules-widen(P1) ·record-share-grant-revoke(P1) ·audit-log-browser(P1) ·owd-save-gate(P1) ·public-form-intake(P1) ·suggested-binding-loop(P2) ·sharing-rule-authoring-ui(P2) ·permission-matrix-edit-loop(P1) ·capability-declaration-lifecycle(P1).Two notes for whoever picks these up.
suggested-binding-loop's clause 0 is a known expected-fail on stock (#7677 — stock auto-binds the onlyisDefaultsuggestion at boot and surfaces no row at all); itsknownGapscarry the unbind sequence that provisions a genuine pending row. And per the item's own N2, an empty list must never be ticked green.Coverage honesty
9 of 20 items consulted; no clause failed; 1 item fully passed, 7 partial, 2 blocked, 10 not-run. The area's two-sided gate discipline held everywhere it was exercised: every refusal above was proven server-side on a forged direct request, with an admin re-read confirming non-mutation, and every guard was paired with the entitled-persona contrast that proves the route is not simply broken (
wrong-persona). No verdict rests on UI absence, and no DOM oracle was used.Fixture gaps found: the tenancy-wall gap above (P0 item unrunnable) and the pre-existing
share-link-capability-tokensblock. Neither is a product defect.#9308remains the home for the seed-shaped second-login gaps — though note thatstack.signUp()/POST /auth/sign-up/emaildoes yield real loginable second personas, which is what let every gate here be driven from both sides without one.Links: #9296 (wave) · #9309 (spec provenance) · #9307 (console-build flake, worked around as documented) · #9308 (fixture card).