Uh oh!
There was an error while loading. Please reload this page.
feat(verify): --rls runs one probe persona per declared POSITION, so app-authored narrowing is exercised (#7978) - #8067
Conversation
The base probe persona holds no positions by construction, so an app policy carrying `positions: [...]` is never applicable to it — the app's own narrowing went unexercised while only the platform gate underneath it was proven. That is the authoring shape the real #7665 defect wore. `objectstack verify --rls` now fans out: one persona per position the app DECLARES (derived from `config.positions`, never a transcribed list), each holding that position and nothing else. Probe targets are established once and shared, each persona writes a distinct short marker, and a position that yielded no verdict is reported rather than dropped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B3Kurx8qufrDzNjk4rag7V
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B3Kurx8qufrDzNjk4rag7V
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 2 package(s): 17 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 4 release-owned page(s) also reference the affected code. These are read-only:
|
hotlong
commented
Aug 12, 2026
PM review — Sequencing ruling: #8059 firstThe report asks for this call, correctly. The answer is not "land this and accept a red dogfood leg":
#8059 goes first, and it deserves to on its own merits regardless of sequencing: a caller who cannot read a record can write it by id, on stock Once #8059 lands, re-run and enqueue this. If the showcase leg is green then, nothing else is needed here. The blocker was reported, not suppressed — which is the entire pointThe dispatch said: "⛔ Never tune the probe to preserve a green. If a position persona finds real holes, that is the deliverable WORKING. Do NOT fix the holes here — report them." All three parts were honoured, and the result is the strongest evidence this lane has produced today that its coverage work pays:
The old green was not wrong about what it measured; it simply never measured this. The derivation and the ablation both hold
The ablation still bites, and one number is the important one: under the #7665 ablation the base persona goes 20 consistent → 4 consistent + 16 holes — identical to #7685's recorded 16. That is not just detector liveness; it is the evidence that the shared-target refactor did not change the base path. Reusing a prior measurement as a fixed point for a refactor is exactly the right instinct.
Two things to carry into the follow-up#8059 must fix both sites together, per the report: #8060 (the CRM example binds none of its 3 declared positions, so 18/18 probes are probe-blocked) is correctly observation-class and left for triage — it explains the crm numbers rather than being a defect this card should chase. The Generated by Claude Code |
…7978) `check:verify-stand-in` discovers every exported `packages/verify` function whose first parameter is annotated with an interface the package declares and publishes, and fails until each is classified. `provisionRlsPositionPersona(stack: VerifyStack)` is the same class as its sibling `provisionRlsProbePersona`: it takes the handle `bootStack` returned and MINTS a persona through it — sign-up via that stack's auth route, one `sys_user_position` row through `stack.kernel`'s ObjectQL service. It checks nothing, so the parameter type is not the compile-time half of any conformance, and reaching the kernel is what a minimal structural surface could never carry. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B3Kurx8qufrDzNjk4rag7V
hotlong
commented
Aug 12, 2026
Classification pushed — Verdict:
So the entry lands in Nothing was silenced or loosened: (7 exempt, up from 6 — mine is the only new candidate; ⛔ Untouched, per your ruling: the probe, the showcase policies and the exit contract. Generated by Claude Code |
hotlong
commented
Aug 12, 2026
PM review — |
| leg | before | now |
|---|---|---|
Dogfood Verify CLI | red — 2 rls-holes | ✅ success |
Dogfood Regression Gate (1/3, 2/3, 3/3) | red | ✅ all success |
Test Core (1/3, 2/3, 3/3) | — | ✅ all success |
The delta is measured, not assumed: the PR's own content did not change. The only edit was update-branch, which merged current main into the head. Same probe, same personas, same exit contract — only the base moved. So the greens are attributable to #8099 and to nothing else.
The two cards prove each other
This is the part worth recording, because it is the whole argument for funding coverage work:
- [finding]
verify --rlscannot reach RLS narrowing authored on a POSITION — one probe persona holds no positions, so position-gated policies never apply to it #7978 was "just" a coverage card —verify --rlscould not reach narrowing authored on a POSITION, because the single probe persona held no positions. Widening it surfaced [security] A check-onlyupdatepolicy suppresses the #7665 write-scope derivation — a contributor by-id-writes showcase_invoice rows it cannot read #8059: a caller whoGETs 404 onshowcase_invoicecouldPATCHit 200 with the row actually changed, including reassigning the owner to itself. - The probe is validated by having found a real defect rather than noise. The fix is validated by the probe going green without the probe being touched.
Neither claim would stand alone. A probe that only ever passes proves nothing about the system; a fix measured only by its own new tests proves nothing about the probe. Here each is the other's independent witness.
⛔ For the record: nothing was done to make these reds go green. The probe, the showcase's policies, the personas and the exit contract are all untouched — the standing instruction on this hold was that the reds were the deliverable's output, not an obstacle to it, and it was honoured. Had anyone "fixed" #8067 to get a green CI, the platform would still be shipping the write-scope hole and we would have destroyed the only instrument that could see it.
Enqueueing
Dogfood Verify CLI green at 16:06:22; every other completed check green. TypeScript Type Check is still running and auto-merge will hold until it converges. Flipping ready and enabling auto-merge — both steps, since flipping out of draft alone does not enqueue.
Generated by Claude Code
Uh oh!
There was an error while loading. Please reload this page.
Fixes#7978
verify --rlsnow exits 1, and that is the deliverable workingThe new personas found two real holes on the stock showcase, on current
main, with #7665's derivation fully in place: acontributorgetsGET 404on ashowcase_invoiceand stillPATCHes it by id, and the same on itscontrolled_by_parentline. Filed as #8059 — ⛔ not fixed here, per the ruling on this card ("never tune the probe to preserve a green"; finding holes is the deliverable working).Consequence:
ci.yml'sdogfood-verifystep runsverify --rlsfor both example apps, so it will be red on the showcase leg until #8059 lands. The crm leg stays green. This is reported, not suppressed — the exit contract counts a position persona's hole exactly like the base persona's, because it is exactly as real.Why
Since #7685 the probe persona authors its own capability (object read+edit, owner-scoped
selectonly), which makes the platform's by-id write gate reachable. It holds no positions by construction, andgetApplicablePoliciesfilters a policy declaringpositions: [...]out for a caller who holds none — so the app's own narrowing was never exercised, only the platform gate underneath it.rls.ts's module header said so; this PR is that gap closed (the header is updated to match).That gap is the exact authoring shape of the real #7665 defect — an ordinary
contributoragainst the showcase'spositions: ['contributor']rules.What changed
config.positions(declaredPositionNames), never a list kept in the verifier, so a position added to an app is covered without touching this package. Each holds that position and nothing else (provisionRlsPositionPersonawrites onesys_user_positionrow), so its whole capability is what the app binds to the position. The built-ineveryone/guestanchors are excluded — no app declares them, and the base persona already carries theeveryonebaseline.maxLengthwould truncate a long one into a false negative.RlsReportgainspositionRuns[],totals(all personas summed; unit is one object × persona probe) andpositionCoverage(declaredvsran,notRunfor a declared position whose persona could not be provisioned, and anotewhen the app declares no positions at all).summary/results/unprovenstill describe the base persona exactly as before, so existing consumers gain fields and lose none.verifyreadstotals.holes, and an unprovisionable declared position is a hard failure for the same reason a degraded base persona is.Measured, before → after
Positions probed: showcase 9 of 9 declared, crm 3 of 3;
notRunempty on both. Cost is well insidedogfood-verify's 20-minute budget (the job was ~4.5 min).Both policies quoted in #7978 are now exercised:
showcase_task(positions: ['contributor'], select-only) is PROVEN for the contributor persona, andshowcase_invoice(same shape plus a check-onlyupdatepolicy) is the hole.The crm's 18
probe-blockedare honest, not noise: that app binds no permission set to any declared position, so a holder gets 403 at the object gate. Recorded separately as #8060.Report tail, showcase:
The fan-out is falsifiable, not decorative
Re-running the #7665 write-scope ablation (the derivation switched off in
plugin-security, the #1994 pre-image re-read left fully in place):contributorpersona'sshowcase_taskflipsrls-consistent→rls-hole, and the crm's base run goes 6 consistent → 6 holes.A persona that cannot detect a known hole would be decoration; this one detects it.
Tests
packages/qa/dogfood/test/rls-runner.test.ts— the runner's detector-liveness oracle, extended for the new persona shape: a position persona can still sayrls-holeand it reachestotals.holes; the object gate answering a position persona isprobe-blocked, never a pass; markers are distinct and the target is created once (the false-hole guard for shared targets); an app declaring no positions says so distinctly and adds nothing toproven; a declared position with no persona lands innotRunwith its reason. PlusdeclaredPositionNamesderivation, including "a position added to the app is covered without editing the verifier". 17 tests pass.pnpm --filter @objectstack/{verify,cli,dogfood} typecheckclean;tsc --noEmit -pmeasures 0 errors for all three, socheck:type-check-debtcannot rise.eslintclean on the changed areas;check:nul-bytesOK.Still out of reach — stated so this green is not over-read either
A persona here holds the bare position, unanchored: narrowing that gates on a position held together with a business-unit anchor, an org membership or a sharing-rule grant is still unexercised, and a position bound to a view-all set (
showcase_auditor,showcase_ops) reads every row by design and reportsmember-visible. That is why coverage is reported per position — the fan-out must never be read as N× the reach. Recorded in the module header and the README.Generated by Claude Code