Repository navigation
Commit 3a440b0
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)
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.ts` is deleted,
along with its six exports from `@objectstack/objectql`:
`PLATFORM_OBJECT_TENANCY`, `classifyPlatformObjectTenancy`,
`isPlatformObjectOutOfTenantAuditScope`, `tenantScopedPlatformObjects`,
`PlatformObjectTenancy` and `PlatformObjectTenancyEntry`.
- **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: false` or 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:
- a write that belongs to one organization should run in that
organization's context;
- a write that crosses organizations on purpose (a platform admin the
organization wall exempts, or a system job over every organization)
needs no fix, and the caller declares it with `bypassTenantAudit: true`.
The line's prefix and metadata are unchanged.
- **Docblocks:** `system-write-organization.ts`, `approval-service.ts`,
`business-unit-graph.ts` and 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, nor #14570 as the
tracking card (that card is no longer open).
- **The census is repurposed, not removed.**
`scripts/platform-object-tenancy-census.mjs` already executed the schema
predicate (`resolveTenantFieldName` on the registered schema) and never
read the ledger. Two things move:
- its `HEADER` pointer now targets `resolveTenantFieldName`'s docblock
in `system-write-organization.ts`;
- the artefact's comment says `reach: in` is 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, and
`packages/cli/src/utils/organization-ownership-inventory.ts`.
## Measured before the change (base `9c431bad`)
- **Readers of `isPlatformObjectOutOfTenantAuditScope`:** one
behavioural reader, `engine.ts`'s `isTenantAuditInScope`. It only
decided `opts.bypassTenantAudit`, which the SQL driver reads in
`auditMissingTenant` to skip a WARN. Diagnostics only. The system-insert
rule had no ledger reader left (stage 8b and #24667).
- **Importers of the six exports outside `@objectstack/objectql`:**
- `scripts/audits/14096-tenancy-ledger-namespace-regexp-census.mjs`, a
dated one-off audit that imports `dist`. Nothing runs it (no
`package.json` script, no workflow, no gate roster). It now refuses
loudly at import, which its own docblock prescribes for a missing
ledger.
- No package imports them.
- Because they are published names, removing them is still declared a
narrowing.
## 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:403` and
`docs/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 Core` and
`Governed Surface Queue Guard`, plus `Check Changeset`.
- **Pins, with ablations** (the commit was in place first; each restore
is `git checkout HEAD -- <path>`, and the tree was clean afterwards):
- **The mute is decided by the schema.**
`tenancy-by-object-classification.test.ts` passes 28/28. Ablation: I
re-added a per-table exemption for `sys_audit_entry` in
`buildDriverOptions`. 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 stamped `tenantId`) for `sys_audit_entry` itself. So a per-table
entry changes no write; it changes only the mute.
- **The ledger's exports are gone.** A tombstone pin checks that the
public API has no `PlatformObject*` name.
- **The warning's new text.** `sql-driver-tenant-audit-posture.test.ts`
passes 12/12. Ablation: restoring base `sql-driver.ts` turns exactly the
new pin red (1 failed, 11 passed).
- **Package suites and typecheck, all exit 0:**
- objectql: 8643 passed;
- driver-sql: 3749 passed, 212 skipped (live-dialect cells);
- plugin-approvals: 1213;
- plugin-sharing: 1200;
- service-messaging: 628;
- service-storage: 1130.
- **`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 -- --fresh` gave health 200. The seeded
admin signs in (200) and reads `showcase_account` (200, 14 rows). There
are 0 `[tenant-audit]` lines, since `single` does not audit.
- **R4 (walled `os serve`):**
- **Setup:** `OS_TENANCY_POSTURE=isolated` with a fresh SQLite file,
`OS_SECRET_KEY`, `OS_AUTH_MEMBERSHIP_POLICY=invite-only` and
`OS_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.
- **Result:** health 200, no refusal. The owner signs up (200), signs in
(200) and reads `sys_user` (200).
- **What the audit printed:** 43 `[tenant-audit]` lines over boot and
sign-up, every one with the new text, across 22 objects. 18 are
`showcase_*` app objects, which were audited before too. The 4 platform
objects now audited because the ledger's mute is gone are
`sys_activity`, `sys_business_unit`, `sys_member` and
`sys_user_permission_set`.
- **Upgrade harness:**
`packages/qa/dogfood/test/upgrade-18x-harness.dogfood.test.ts` passes
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_id` on the row: under a wall an organization-less system
insert is refused (D9), and a row that names its organization reaches
the driver with no `tenantId` (pinned in
`tenancy-by-object-classification.test.ts`). Examples are `create` on
`sys_business_unit` and `bulkCreate` on `showcase_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](https://claude.ai/code/session_01DWpoqyLrN4CEQFhSjFddQ9)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 123ff6f commit 3a440b0
14 files changed
Lines changed: 263 additions & 501 deletions
File tree
- .changeset
- packages
- drivers/driver-sql/src
- objectql/src
- tenancy
- plugins
- plugin-approvals/src
- plugin-sharing/src
- scripts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
199 | 199 | | |
200 | 200 | | |
201 | 201 | | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15980 | 15980 | | |
15981 | 15981 | | |
15982 | 15982 | | |
| 15983 | + | |
| 15984 | + | |
| 15985 | + | |
| 15986 | + | |
| 15987 | + | |
| 15988 | + | |
| 15989 | + | |
| 15990 | + | |
| 15991 | + | |
15983 | 15992 | | |
15984 | 15993 | | |
15985 | 15994 | | |
| |||
16008 | 16017 | | |
16009 | 16018 | | |
16010 | 16019 | | |
16011 | | - | |
| 16020 | + | |
| 16021 | + | |
| 16022 | + | |
| 16023 | + | |
| 16024 | + | |
16012 | 16025 | | |
16013 | 16026 | | |
16014 | 16027 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
220 | 220 | | |
221 | 221 | | |
222 | 222 | | |
223 | | - | |
224 | | - | |
225 | | - | |
226 | 223 | | |
227 | 224 | | |
228 | 225 | | |
| |||
7536 | 7533 | | |
7537 | 7534 | | |
7538 | 7535 | | |
7539 | | - | |
7540 | | - | |
7541 | | - | |
7542 | | - | |
7543 | | - | |
| 7536 | + | |
| 7537 | + | |
| 7538 | + | |
| 7539 | + | |
| 7540 | + | |
| 7541 | + | |
| 7542 | + | |
| 7543 | + | |
| 7544 | + | |
7544 | 7545 | | |
7545 | 7546 | | |
7546 | | - | |
7547 | | - | |
| 7547 | + | |
7548 | 7548 | | |
7549 | 7549 | | |
7550 | 7550 | | |
| |||
7563 | 7563 | | |
7564 | 7564 | | |
7565 | 7565 | | |
7566 | | - | |
7567 | | - | |
7568 | | - | |
| 7566 | + | |
| 7567 | + | |
| 7568 | + | |
7569 | 7569 | | |
7570 | 7570 | | |
7571 | 7571 | | |
| |||
7575 | 7575 | | |
7576 | 7576 | | |
7577 | 7577 | | |
7578 | | - | |
7579 | | - | |
7580 | | - | |
7581 | | - | |
| 7578 | + | |
| 7579 | + | |
| 7580 | + | |
| 7581 | + | |
7582 | 7582 | | |
7583 | 7583 | | |
7584 | 7584 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
553 | 553 | | |
554 | 554 | | |
555 | 555 | | |
556 | | - | |
557 | | - | |
558 | | - | |
559 | | - | |
560 | | - | |
561 | | - | |
562 | | - | |
563 | | - | |
564 | | - | |
565 | | - | |
566 | | - | |
567 | | - | |
568 | 556 | | |
569 | 557 | | |
570 | 558 | | |
| |||
0 commit comments