Repository navigation
refactor(objectql)!: retire the PLATFORM_OBJECT_TENANCY ledger — the tenant-audit mute is decided by the schema, and the [tenant-audit] warning names both readings - #25210
Conversation
…enant-audit mute is read off the schema (WIP) Claude-Session: https://claude.ai/code/session_01DWpoqyLrN4CEQFhSjFddQ9 Co-authored-by: Claude <noreply@anthropic.com>
…er's retirement; changeset Claude-Session: https://claude.ai/code/session_01DWpoqyLrN4CEQFhSjFddQ9 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 4 package(s): 25 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 12 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 30 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin a7059442db1ad2925cb5b1c259ad83b67a208ae1 && git checkout a7059442db1ad2925cb5b1c259ad83b67a208ae1
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e5f194ba13d8d9a2818c1ece98a9c206fa0c7623 aaa6ef6aa5ab46a82ba247dd58d6a2f92a304626 && git checkout -B drift-repro e5f194ba13d8d9a2818c1ece98a9c206fa0c7623 && git merge --no-ff aaa6ef6aa5ab46a82ba247dd58d6a2f92a304626
node scripts/docs-audit/affected-docs.mjs --json e5f194ba13d8d9a2818c1ece98a9c206fa0c7623
|
Contract reviewServed-tier: Read as: card #25201 (body, both comments), the reconciliation note 6339761793 on #15212, ADR-0131 D9/D13 on origin/main, PR #25210's body and file list, the net diff 9c431ba..aaa6ef6 (14 files, +263/-501; merge-base re-confirmed against origin/main 15764cd), and the check-runs on the head. Check-runs, latest run per name (41 names): every one is success or skipped. ① Derived judgments
② Semver levelRIGHT. .changeset/25201-platform-object-tenancy-ledger-retired.md grades ③ Boundary flagsThe dev's open_questions is empty. The dev's three out_of_scope_findings, restated in the PR body's Acceptance notes and Observation, each answered or escalated:
Implemented-by: VERDICT: PASS |
|
Seat ACCEPT · epic PM Seat check
|
Fixes #25201
Part of the ADR-0131 18.x line (owed scope of #15212, D13). Takes #25203's item 4 (the stale "tracked as #14570" docblocks).
Clause-②: no (narrowing)
Metadata-protocol: none
What changes
The per-table ledger is retired.
packages/objectql/src/tenancy/platform-object-tenancy.tsis deleted, along with its six exports from@objectstack/objectql:PLATFORM_OBJECT_TENANCY,classifyPlatformObjectTenancy,isPlatformObjectOutOfTenantAuditScope,tenantScopedPlatformObjects,PlatformObjectTenancyandPlatformObjectTenancyEntry.The tenant-audit mute is decided by the schema. In
Engine.buildDriverOptions, an elevated write is auto-muted (bypassTenantAudit: true) only when the object has no tenant column (resolveTenantFieldName(schema) === null, i.e.tenancy.enabled: false,systemFields.tenant: falseor no tenant field) or is federated. No name and no list decides it. That is ADR-0131 D9's "column ⇒ in scope", now applied to the audit as well.The
[tenant-audit]warning (SqlDriver.auditMissingTenant) no longer tells every unscoped write to "Pass tenantId". It names both readings, because the driver cannot tell them apart:bypassTenantAudit: true.The line's prefix and metadata are unchanged.
Docblocks:
system-write-organization.ts,approval-service.ts,business-unit-graph.tsand the B3 test comment now describe the post-ADR-0131 write paths. Seed replay (C1) and elevated inserts (D9) stamp the organization, so a NULL member row is pre-18.0 residue or a lean composition. They no longer cite the ledger, norsys_business_unit_memberis unadjudicated inPLATFORM_OBJECT_TENANCY, so seed-replayed and system-written membership rows land organization-less #14570 as the tracking card (that card is no longer open).The census is repurposed, not removed.
scripts/platform-object-tenancy-census.mjsalready executed the schema predicate (resolveTenantFieldNameon the registered schema) and never read the ledger. Two things move:HEADERpointer now targetsresolveTenantFieldName's docblock insystem-write-organization.ts;reach: inis the whole of the engine's tenancy scope.The artefact is regenerated (the comment only; no row moved: 84 / 41 / 43).
lint.yml's step runs it unchanged and green, so no governed-path change is needed. Its stated readers: that gate, andpackages/cli/src/utils/organization-ownership-inventory.ts.Measured before the change (base
9c431bad)isPlatformObjectOutOfTenantAuditScope: one behavioural reader,engine.ts'sisTenantAuditInScope. It only decidedopts.bypassTenantAudit, which the SQL driver reads inauditMissingTenantto skip a WARN. Diagnostics only. The system-insert rule had no ledger reader left (stage 8b and feat(objectql,cli,plugin-webhooks)!: 18.x step 4: the walled lift (8b walled half, the os serve lift, the declared-webhook name guard, declared webhooks deployment-wide) #24667).@objectstack/objectql:scripts/audits/14096-tenancy-ledger-namespace-regexp-census.mjs, a dated one-off audit that importsdist. Nothing runs it (nopackage.jsonscript, no workflow, no gate roster). It now refuses loudly at import, which its own docblock prescribes for a missing ledger.Acceptance notes
git grep PLATFORM_OBJECT_TENANCY, outside CHANGELOGs, still finds files this PR may not or should not touch:.claude/skills/pm-dispatch/references/platform-readings.md:403anddocs/adr/0131…:878(governed);docs/audits/2026-09-tenancy-ledger-namespace-regexp-census.md(a dated audit record);scripts/audits/14096-…mjs(above).Also outside this card's surface:
packages/spec/src/automation/schedule-organization.zod.ts:76(and its generated reference page) still cites the deleted file's path.Proof (head
aaa6ef6a)CI: every required context is green:
Lint & Repo Gates,TypeScript Type Check,Test Core(all six shards),Build CoreandGoverned Surface Queue Guard, plusCheck Changeset.Pins, with ablations (the commit was in place first; each restore is
git checkout HEAD -- <path>, and the tree was clean afterwards):tenancy-by-object-classification.test.tspasses 28/28. Ablation: I re-added a per-table exemption forsys_audit_entryinbuildDriverOptions. Exactly 2 pins went red: "sys_audit_entry … is NOT auto-muted" and "the SAME driver options — the name decides nothing". The other 26 stayed green, among them every write pin (derive, refusals, the stampedtenantId) forsys_audit_entryitself. So a per-table entry changes no write; it changes only the mute.PlatformObject*name.sql-driver-tenant-audit-posture.test.tspasses 12/12. Ablation: restoring basesql-driver.tsturns exactly the new pin red (1 failed, 11 passed).Package suites and typecheck, all exit 0:
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 100 commands, each run with its exit code recorded before any pipe.--ran: "100 derived famil(ies) accounted for — 99 run, 1 NOT-MEASURED". Two exits are non-zero, both shallow-clone prerequisites:check-plugin-teardown-shape --self-test: exit 3, prerequisite not met;check-engine-split-ratio --days 90: exit 2, refuses on a shallow clone.The census gate is green in both modes: 84 objects, 41 in reach, 43 out, every exclusion explained.
check-governed-merges --branch: NOT governed. +263/−501 over 14 files.R1 (fresh boot):
pnpm dev -- --freshgave health 200. The seeded admin signs in (200) and readsshowcase_account(200, 14 rows). There are 0[tenant-audit]lines, sincesingledoes not audit.R4 (walled
os serve):OS_TENANCY_POSTURE=isolatedwith a fresh SQLite file,OS_SECRET_KEY,OS_AUTH_MEMBERSHIP_POLICY=invite-onlyandOS_PLATFORM_OWNER_EMAIL. The showcase declares no@objectstack/organizations, so for this run only I declared it locally and linked the workspace package. Both are reverted, and the tree is clean.sys_user(200).[tenant-audit]lines over boot and sign-up, every one with the new text, across 22 objects. 18 areshowcase_*app objects, which were audited before too. The 4 platform objects now audited because the ledger's mute is gone aresys_activity,sys_business_unit,sys_memberandsys_user_permission_set.Upgrade harness:
packages/qa/dogfood/test/upgrade-18x-harness.dogfood.test.tspasses 15/15. That is 8 tests on the 18.0 fixture (ceremony, boot, data) plus 7 on the fixture-18.0.0 leg (boots HEAD with no refusal, no destructive drift, rows intact, the D1 advisory).Observation, not filed
On R4 several of those lines are creates. They must carry
organization_idon the row: under a wall an organization-less system insert is refused (D9), and a row that names its organization reaches the driver with notenantId(pinned intenancy-by-object-classification.test.ts). Examples arecreateonsys_business_unitandbulkCreateonshowcase_account. For an insert the row's own column is the scope, so these lines are noise. That was already true for application objects before this PR; it now covers those 4 platform objects as well. A follow-up could have the driver skip the audit on an insert whose row carries its tenant field. That is the 8c-1 design space, outside this card.Generated by Claude Code