Uh oh!
There was an error while loading. Please reload this page.
docs(audits): #14096 census — tenancy ledger disagrees with namespace regexp on 8 objects - #15122
Conversation
…8 objects Ruling item 1 (director seat, 总监批 #25, 2026-09-01, maintainer verbatim 「同意」): count how many platform-namespace objects the hand-adjudicated tenancy ledger (`PLATFORM_OBJECT_TENANCY`) judges differently than the namespace regexp `/^(sys_|cloud_|ai_)/` that both seed paths still use. Result: 8, not 0 — sys_file, sys_upload_session, sys_approval_request, sys_approval_action, sys_approval_approver, sys_automation_run, sys_notification_delivery, sys_record_share. Per ruling item 3 this STOPS here: no option (A/B/C) is implemented, no seed path is touched, no row on disk is touched. The full predicate, the header control (84/25/59 reproduced), the three-site re-derivation and the disagreement table are in the audit doc; the census script is self-contained and re-runnable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
…nancy-ledger-vs-namespace-census
…read Reviewer on PR #15122 reproduced a silent-failure mode: emptying PLATFORM_OBJECT_TENANCY to {} and rebuilding still prints reproduced_matches_header: true and exits 0, with disagreement_count silently reading 0 -- indistinguishable from a genuine zero. The header control never reads the ledger (it's an AST census of the object files), so it validates the population and tenant-field logic and proves nothing about the ledger import. Add a hard floor (LEDGER_ENTRY_COUNT_FLOOR = 5, current ledger has 9 entries) on the imported PLATFORM_OBJECT_TENANCY: below it, the script throws a loud, explicit error instead of reporting a clean zero. Also correct the audit doc's "its other outputs can be trusted" sentence to say precisely what the control does and does not cover, and add a "What this census does not claim" section scoping the result (does not measure actual row counts on any deployment, or when each ledger verdict was decided relative to the seed paths' code) without softening it -- the count is still 8, confirmed three ways. Reproduced the reviewer's break on this branch (empty ledger -> rebuild -> exit 0, disagreement 0), applied the fix, reproduced again (same mutation -> rebuild -> now exit 1, loud error), then restored the ledger file and proved the restore by git hash-object equality against git rev-parse HEAD, plus empty git diff HEAD and git status --porcelain. Re-ran on the real, rebuilt ledger: still reports 8. Part of #14096 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
My own error-message string in the last commit wrote the rebuild hint as `pnpm --filter '@objectstack/objectql^...' build`, using an escaped single quote (\') around the filter target. check:pnpm-filter-targets scans raw JS source text (masking comments, not string-literal escapes), so it read the escape sequence as `--filter \` and correctly flagged that as a filter naming no real package -- a real, self-inflicted finding, not a bug in the gate. Reworded to reference the existing documented commands (the module doc comment above, and the audit doc's "Commands to re-run this census" section) instead of respelling the pnpm invocation inline. Re-ran check:pnpm-filter-targets: green. Re-ran the script against the real ledger: still reports 8. Part of #14096 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
os-musk
commented
Sep 4, 2026
Review FAIL cleared — all three findings closed, two of them verified again by this seatIndependent review returned FAIL on three findings. All three are closed. The review's substantive verdict was that the census itself is sound, and that stands: the count, the predicate, all 13 F1 — |
Uh oh!
There was an error while loading. Please reload this page.
Part of #14096 — the census, per the director-seat ruling's step 1. Non-zero result: this STOPS here, nothing is implemented. (Not
Fixes— a closing keyword on a card the ruling itself sends back to the decision box would delete the decision's home.)The ruling, verbatim (总监批 #25, 2026-09-01, maintainer verbatim 「同意」, comment 5494594783)
Predicate (stated, not left implicit)
The regexp is applied at both seed sites as a uniform rule: every namespace-matching name stays global/cross-tenant — that's the whole population's regexp verdict, always "out of scope". The ledger's verdict,
classifyPlatformObjectTenancy(name), is one oftenant-scoped/global/unclassified.globalandunclassifiedboth map to "out of scope" (the ledger's own file documentsunclassifiedas preserving today's — i.e. the same — behaviour). So a disagreement is exactly a ledger entry classifiedtenant-scoped.Result: 8, not 0
sys_filetenant-scopedsys_upload_sessiontenant-scopedsys_approval_requesttenant-scopedsys_approval_actiontenant-scopedsys_approval_approvertenant-scopedsys_automation_runtenant-scopedsys_notification_deliverytenant-scopedsys_record_sharetenant-scopedCross-checked against the ledger's own pinned test (
tenancy-by-object-classification.test.ts'stenantScopedPlatformObjects()), which lists the identical 8 names, and computed live from the real, built@objectstack/objectqlmodule (not re-transcribed by hand).Positive control (dispatch Zone 2.3) — what it covers, and what it doesn't
The ledger's header (
platform-object-tenancy.ts:32-37) claims 84 platform-namespace objects registered, 25 resolving no tenant field, 59 carrying one. This census's script reproduces those exact numbers — after catching its own precedence bug (a first draft mishandledtenancy.enabled: falseas overridable by a self-declared field, the way themanagedByskip is;resolveTenantFieldNameactually checkstenancy.enabled === falsefirst and unconditionally, before ever readingfields). The corrected script matches 84/25/59 exactly — left as a comment in the script since it's exactly the discipline the dispatch asks for.Scope of this control, stated precisely (review round 2, see below): it is an AST census of the object files — it never reads
PLATFORM_OBJECT_TENANCYat all. It validates the population and the tenant-field precedence logic (and is what caught the precedence bug above); it validates nothing about the ledger import that the disagreement count in the table above is computed from. That gap is now covered by a hard floor in the script itself (see "Review round 2" below), not by this control passing.Also avoided: the
managedBy: 'better-auth'trap that caught a prior census this week (sys_member/sys_team/sys_invitationresolve a real tenant field despite that label — confirmed, and confirmed they don't change the disagreement count since all three areunclassifiedin the ledger, which already agrees with the regexp).Three sites re-derived (dispatch Zone 2.5)
Card measured these at
dda969cd71; this branch is off35e94c96b(confirmed an ancestor). All three unchanged in shape:engine.ts:3744/:3963(called from:9934) —isPlatformObjectOutOfTenantAuditScope, the per-object ledger read.seed-loader.ts:926—fallbackOrgId, still gated by the namespace regexp.seed-tenancy-backfill.ts:587,1251— its ownPLATFORM_NAMESPACE, still filtering by it.Branch verdict and context that raises the stakes
Non-zero ⇒ STOP per ruling item 3. No option implemented; no seed path touched; no row on disk touched (ruling item 4's counter-precedent: #8686's own posture is "already-minted rows are reported, never rewritten"). The card returns to the decision box, re-graded p1 — and confirmed not alone: #13636 (the third tenancy state) is still open,
pm:awaiting-maintainer+priority:p1, its implementation PR #14923 closed unmerged on the maintainer's own instruction this week. So this lands beside a card already in the maintainer's hands.What this census does not claim (added, review round 2)
The disagreement is real and the count is 8 — three independent routes agree on it (a hand count, the ledger's own pinned test, and the script). This is not softened by scoping it: this census measured a static ledger-vs-regexp classification disagreement, and it did not measure (a) how many rows the two seed paths have actually written for these 8 objects on any deployment, or (b) when each ledger verdict was decided relative to the seed paths' code — i.e. whether the ledger later tightened and the seed paths didn't follow, or the seed paths were already out of step when written. Those have different remedies and neither was measured. Full paragraph in the audit doc's new
## 7. What this census does not claim.Review round 2 — two findings fixed
Independent review reproduced a silent-failure mode in the census script: emptying
PLATFORM_OBJECT_TENANCYto{}, rebuilding, and re-running still printedreproduced_matches_header: trueand exited 0, withdisagreement_countsilently reading 0 — indistinguishable from a genuine zero, and the number this ruling branches on (a false zero would close a p1 tenancy question as "decided, nothing to do"). Root cause: the header control never reads the ledger at all (see the corrected Positive-control section above), so it validates nothing about the ledger import.Fix: the script now asserts a hard floor (
LEDGER_ENTRY_COUNT_FLOOR = 5; the real ledger holds 9 entries) on the importedPLATFORM_OBJECT_TENANCYand throws a loud, explicit error below it, instead of reporting a clean zero. Reproduced the reviewer's exact break on this branch (emptied ledger → rebuilt → before the fix: exit 0, disagreement 0; after the fix: exit 1, loud error naming the cause and the remedy), then restored the ledger file and proved the restore bygit hash-objectequality againstgit rev-parse HEAD:<path>, an emptygit diff HEAD, and an emptygit status --porcelain— never by exit code. Re-ran on the real, rebuilt ledger: still reports 8.Also added the doc's missing "what this census does not claim" scope paragraph (above), and corrected the doc's "its other outputs can be trusted" sentence about the positive control to say precisely what it does and does not cover.
(Self-inflicted and self-caught in the same round: the first draft of the new error message spelled the rebuild hint as
pnpm --filter '@objectstack/objectql^...' build, whose escaped quote madecheck:pnpm-filter-targetsread--filter \as a dead filter target. Reworded to reference the already-documented commands instead of respelling the invocation inline; gate re-run green.)Clause-② and changeset
Clause-②: no (mechanical floor re-checked against the final diff: no exported symbol, no published payload key — two files under
docs/audits/andscripts/audits/only, unchanged in this round).skip-changeset: a stop-at-non-zero outcome with no code change to a shipped package is exactly the case the dispatch names for this label.Gates —
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands(no paths, derived off merge-basef594e70d7, same 2-file set at final HEAD)All 25 derived commands, plus the 6 flagged "roster lives under
scripts/, which this diff touches" (check-published-list-mirrors.mjs×2,check:console-injection,check:engine-double-contract,check:i18n-stale-fill,check:published-readme-exports), pluscheck-governed-merges.mjs --teston the final file list:check:console-injectionexits 0 and informationally skips its injection check — no builtpackages/console/dist, which needs the separateobjectui:build, not part ofpnpm build; unrelated to this diff either way).check:published-readme-exportsinitially failed on an unbuilt tree (86 packages'dist/*.d.tsmissing) — a fullpnpm build(72/72 tasks) resolved it:198/198 @objectstack/ specifier(s)resolved, 0 baseline instances.check:pnpm-filter-targetsinitially failed against this round's own new error-message string (see "Review round 2" above) — fixed and re-run green.check-governed-merges.mjs --test: NOT governed — 0/2 paths hit the register (5 surfaces).No governed surface touched. No
packages/objectql/src/engine.tsorpackages/metadata-protocol/src/protocol.tsedit (both serial-constrained, confirmed untouched this round too).🤖 Generated with Claude Code
https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68