Skip to content

Commit 5629e6f

Browse files
committed
feat(scripts): derive and gate the platform-object tenancy census (#14957)
`PLATFORM_OBJECT_TENANCY`'s header stated the census as prose — three hand-written digits plus a parenthetical attributing them — with nothing re-deriving it. It failed in both directions a prose count can. A wrong REASON behind a right total: the parenthetical named `sys_sso_provider`'s `tenancy.enabled: false` as an addition to the `managedBy: 'better-auth'` set it was already in, and left `sys_api_key`'s identical opt-out unnamed. `24 + 1 = 25` stayed right, so neither a reader nor a gate caught it; that count reached a PR body and a filed card, and two independent re-measurements were spent proving a correct file correct. Then a stale TOTAL: PR #15155 declared `systemFields: { tenant: false }` on `sys_metadata_activation`, the object left the machinery's reach, and the ungated prose kept the old digits while the same commit updated the gated page next door. CI was green throughout. The digits are deleted, not corrected. The header points at the derived artefact and states the predicate it was missing: `resolveTenantFieldName` answering non-null on the REGISTERED schema, after `applySystemFields` has injected the tenant column, because the injected column is what the engine sees. `managedBy` is not the predicate. The generator loads `resolveTenantFieldName` and `resolveInjectedSystemColumns` from source and EXECUTES them rather than re-spelling what they decide — a transcription would be a second copy of a rule the engine owns, free to drift while reading as authoritative. It needs no build, so the gate runs in the `lint` job. Each excluded object records the declarations on its OWN schema, verbatim and not mutually exclusive: an object carrying two keeps both, which is what makes the mis-attribution unrepeatable. An excluded object with no declared mechanism is an ERROR — the generator refuses to write the row and the gate reds — so a new exclusion mechanism is adjudicated, never absorbed into an existing bucket's total. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
1 parent 845d767 commit 5629e6f

7 files changed

Lines changed: 1703 additions & 13 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
"@objectstack/objectql": patch
3+
---
4+
5+
The platform-object tenancy census is derived and gated instead of hand-written in a comment. Documentation only — no runtime behaviour changes.
6+
7+
`PLATFORM_OBJECT_TENANCY`'s header explained why the reclassification needs a ledger rather than a schema read, and backed the argument with three hand-written digits and a parenthetical attributing them. Nothing re-derived any of it, so it was true only until the population moved and failed silently when it did — in both of the directions a prose count can.
8+
9+
The parenthetical mis-attributed the exclusion: it named `sys_sso_provider`'s `tenancy.enabled: false` as an addition to the `managedBy: 'better-auth'` set that object was already in, and left `sys_api_key`'s identical opt-out unnamed. The arithmetic stayed right, which is why no reader and no gate caught it — a wrong reason producing a right total is the shape that survives longest. The digits then went stale when an object opted out of the tenant column through a third mechanism the parenthetical's taxonomy had no slot for (`systemFields: { tenant: false }`), while the gated page next door was updated in the same commit.
10+
11+
The digits and the parenthetical are deleted rather than corrected. The header now points at `scripts/platform-object-tenancy-census.json` and states the PREDICATE it was missing: an object is inside the machinery when `resolveTenantFieldName` answers non-null on the **registered** schema — after `applySystemFields` has injected the tenant column, because the injected column is what the engine sees, not what the author typed. Counting `managedBy` as if the resolver read it is the mistake that produced the wrong reason.
12+
13+
The artefact is derived by `scripts/platform-object-tenancy-census.mjs`, which loads `resolveTenantFieldName` and `resolveInjectedSystemColumns` from source and executes them rather than re-spelling what they decide, and is held to the tree by `scripts/check-platform-object-tenancy-census.mjs`. It records per object the declaration on that object's own schema that puts it outside the reach; declarations are not mutually exclusive and an object carrying two keeps both. An excluded object with no declared mechanism is an error, not a default: the generator refuses to commit the row and the gate reds, so a new exclusion mechanism is adjudicated rather than absorbed into an existing total.

.github/workflows/lint.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1593,6 +1593,54 @@ jobs:
15931593
node scripts/check-tenant-audit-census.mjs --self-test
15941594
node scripts/check-tenant-audit-census.mjs
15951595
1596+
# #14957 platform-object tenancy census.
1597+
# `scripts/platform-object-tenancy-census.json` carries which
1598+
# platform-namespace objects the tenancy machinery can REACH, and the
1599+
# declaration on each excluded object's own schema that puts it outside.
1600+
#
1601+
# ⭐ It exists because the same census was PROSE, in the header of
1602+
# `packages/objectql/src/tenancy/platform-object-tenancy.ts`, and failed in
1603+
# both available directions inside one month. First a wrong REASON behind a
1604+
# right total: the parenthetical named `sys_sso_provider` as an addition to
1605+
# the `managedBy: 'better-auth'` set it was already in, and `24 + 1 = 25`
1606+
# stayed right, which is why neither a reader nor a gate caught it — that
1607+
# count reached a PR body and a filed card, and two independent
1608+
# re-measurements were spent proving a correct file correct. Then a stale
1609+
# TOTAL: PR #15155 declared `systemFields: { tenant: false }` on
1610+
# `sys_metadata_activation`, the object left the machinery's reach, and the
1611+
# ungated prose kept the old digits while the same commit updated the GATED
1612+
# page next door. CI was green throughout both.
1613+
#
1614+
# ⚠️ The PREDICATE is the deliverable and the number is a by-product:
1615+
# `resolveTenantFieldName` answering non-null on the REGISTERED schema
1616+
# (after `applySystemFields` injects the tenant column). A gate that
1617+
# re-derives the number without stating the predicate would recreate the
1618+
# same trap with fresher digits, and `managedBy` — the thing the prose
1619+
# counted as if the resolver read it — is not the predicate.
1620+
#
1621+
# ⭐ Its third verdict is the one worth having: an object outside the reach
1622+
# with NO declared mechanism to explain it is an ERROR, not a default. That
1623+
# is what fires on a new exclusion mechanism the day it lands, instead of
1624+
# letting it be absorbed into an existing bucket's total.
1625+
#
1626+
# It is a DRIFT gate, not an anchor gate: the artefact carries no line
1627+
# numbers, so displacement cannot move it, and there is exactly one
1628+
# mechanical repair path (`node scripts/platform-object-tenancy-census.mjs
1629+
# --write`). It lives in this job, with no paths filter, because the PRs
1630+
# that move this population are code-only ones that add or re-declare an
1631+
# object. The `--self-test` invocation is what `check:self-test-wired`
1632+
# requires and is the only instrument on this gate's two matching rules;
1633+
# a clean tree cannot tell a working rule from a weakened one.
1634+
#
1635+
# Invoked as `node` rather than through a `pnpm check:*` alias: see the
1636+
# GATE INVOCATION IDIOM note at the top of this file. TypeScript transpile
1637+
# of the predicate's import graph plus every tracked object declaration,
1638+
# no build and no spawns; ~10s.
1639+
- name: Platform-object tenancy census matches the tree
1640+
run: |
1641+
node scripts/check-platform-object-tenancy-census.mjs --self-test
1642+
node scripts/check-platform-object-tenancy-census.mjs
1643+
15961644
# #12236 one <h1> per doc page. `DocsTitle` renders the frontmatter `title`
15971645
# as the page's <h1> unconditionally (apps/docs/app/[lang]/docs/[[...slug]]/
15981646
# page.tsx), so a body-level `# ` heading compiles to a SECOND one inside

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,9 @@
157157
"check:stall-guard-headroom": "node scripts/measure-stall-guard-headroom.mjs --self-test",
158158
"check:swallow-census-controls": "node scripts/measure-durability-swallow-family.mjs --self-test=gated",
159159
"gen:system-context-census": "node scripts/check-system-context-census.mjs --fix",
160-
"check:system-context-census": "node scripts/check-system-context-census.mjs --self-test && node scripts/check-system-context-census.mjs"
160+
"check:system-context-census": "node scripts/check-system-context-census.mjs --self-test && node scripts/check-system-context-census.mjs",
161+
"gen:platform-object-tenancy-census": "node scripts/platform-object-tenancy-census.mjs --write",
162+
"check:platform-object-tenancy-census": "node scripts/check-platform-object-tenancy-census.mjs --self-test && node scripts/check-platform-object-tenancy-census.mjs"
161163
},
162164
"keywords": [
163165
"objectstack",

packages/objectql/src/tenancy/platform-object-tenancy.ts

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,33 @@
2626
* measurement is the reason this file exists. `applySystemFields`
2727
* (`registry.ts`) provisions the tenant COLUMN unconditionally: its existence
2828
* was deliberately decoupled from whether tenancy is on, so that sudo writers
29-
* can always stamp it. Measured on this tree by AST census of every
30-
* `ObjectSchema.create` in `packages/`:
31-
*
32-
* - 84 platform-namespace objects are registered in this repository;
33-
* - 25 resolve NO tenant field (24 `managedBy: 'better-auth'`, plus
34-
* `sys_sso_provider`'s `tenancy.enabled: false`) and are already outside
35-
* the machinery — they exit at `resolveTenantFieldName` returning null;
36-
* - **59 carry a tenant column**, `sys_permission_set` — #8672's own example
37-
* of a legitimately org-less object — among them.
38-
*
39-
* So a schema read admits 59 of 84 in one stroke, i.e. it replaces a wholesale
40-
* exemption with a wholesale inclusion. The ruling's classification source is
29+
* can always stamp it.
30+
*
31+
* ⛔ The census that measures this is NOT restated here. It is derived by
32+
* `scripts/platform-object-tenancy-census.mjs`, committed as
33+
* `scripts/platform-object-tenancy-census.json`, and held to the tree by
34+
* `scripts/check-platform-object-tenancy-census.mjs` (#14957). Its PREDICATE is
35+
* the half this paragraph used to leave out: an object is inside the machinery
36+
* when `resolveTenantFieldName` answers non-null on the registered schema —
37+
* after `applySystemFields`, because the injected column is what the engine
38+
* sees, not what the author typed. The artefact records, per object, the
39+
* declaration on its OWN schema that puts it outside.
40+
*
41+
* ⛔ Do not restate a count here "for reference". This paragraph carried three
42+
* hand-written digits and a parenthetical explaining them, and each half failed
43+
* in its own direction. The parenthetical mis-attributed the exclusion — it
44+
* named an object as an addition to the set it was already in — while its
45+
* arithmetic stayed right, which is why nothing caught it. The digits then went
46+
* stale when an object opted out of the tenant column (`systemFields: { tenant:
47+
* false }`, a third mechanism the parenthetical's taxonomy had no slot for), and
48+
* CI was green throughout. A number in this comment has no second party; the
49+
* artefact has a gate.
50+
*
51+
* The ARGUMENT survives that measurement, and no digit was load-bearing for it:
52+
* the great majority of platform-namespace objects carry the tenant column —
53+
* `sys_permission_set`, #8672's own example of a legitimately org-less object,
54+
* among them — so a schema read admits nearly all of them in one stroke, i.e.
55+
* it replaces a wholesale exemption with a wholesale inclusion. The ruling's classification source is
4156
* "有列**且有写手填**" — the column AND a writer that fills it — and the second
4257
* half is not a runtime fact. It is a fact about the CODE, established once by
4358
* inventory and written down here.

0 commit comments

Comments
 (0)