Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .changeset/platform-object-tenancy-census-derived.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@objectstack/objectql": patch
---

The platform-object tenancy census is derived and gated instead of hand-written in a comment. Documentation only — no runtime behaviour changes.

`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.

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.

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.

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.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,4 @@ content/docs/references/** merge=os-regen
content/docs/permissions/system-context.mdx merge=os-regen
skills/*/references/_index.md merge=os-regen
skills/objectstack-ui/references/react-blocks.md merge=os-regen
scripts/platform-object-tenancy-census.json merge=os-regen
48 changes: 48 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1593,6 +1593,54 @@ jobs:
node scripts/check-tenant-audit-census.mjs --self-test
node scripts/check-tenant-audit-census.mjs

# #14957 platform-object tenancy census.
# `scripts/platform-object-tenancy-census.json` carries which
# platform-namespace objects the tenancy machinery can REACH, and the
# declaration on each excluded object's own schema that puts it outside.
#
# ⭐ It exists because the same census was PROSE, in the header of
# `packages/objectql/src/tenancy/platform-object-tenancy.ts`, and failed in
# both available directions inside one month. First a wrong REASON behind a
# right total: the parenthetical named `sys_sso_provider` as an addition to
# the `managedBy: 'better-auth'` set it was already in, and `24 + 1 = 25`
# stayed right, which is why 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 both.
#
# ⚠️ The PREDICATE is the deliverable and the number is a by-product:
# `resolveTenantFieldName` answering non-null on the REGISTERED schema
# (after `applySystemFields` injects the tenant column). A gate that
# re-derives the number without stating the predicate would recreate the
# same trap with fresher digits, and `managedBy` — the thing the prose
# counted as if the resolver read it — is not the predicate.
#
# ⭐ Its third verdict is the one worth having: an object outside the reach
# with NO declared mechanism to explain it is an ERROR, not a default. That
# is what fires on a new exclusion mechanism the day it lands, instead of
# letting it be absorbed into an existing bucket's total.
#
# It is a DRIFT gate, not an anchor gate: the artefact carries no line
# numbers, so displacement cannot move it, and there is exactly one
# mechanical repair path (`node scripts/platform-object-tenancy-census.mjs
# --write`). It lives in this job, with no paths filter, because the PRs
# that move this population are code-only ones that add or re-declare an
# object. The `--self-test` invocation is what `check:self-test-wired`
# requires and is the only instrument on this gate's two matching rules;
# a clean tree cannot tell a working rule from a weakened one.
#
# Invoked as `node` rather than through a `pnpm check:*` alias: see the
# GATE INVOCATION IDIOM note at the top of this file. TypeScript transpile
# of the predicate's import graph plus every tracked object declaration,
# no build and no spawns; ~10s.
- name: Platform-object tenancy census matches the tree
run: |
node scripts/check-platform-object-tenancy-census.mjs --self-test
node scripts/check-platform-object-tenancy-census.mjs

# #12236 one <h1> per doc page. `DocsTitle` renders the frontmatter `title`
# as the page's <h1> unconditionally (apps/docs/app/[lang]/docs/[[...slug]]/
# page.tsx), so a body-level `# ` heading compiles to a SECOND one inside
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,9 @@
"check:stall-guard-headroom": "node scripts/measure-stall-guard-headroom.mjs --self-test",
"check:swallow-census-controls": "node scripts/measure-durability-swallow-family.mjs --self-test=gated",
"gen:system-context-census": "node scripts/check-system-context-census.mjs --fix",
"check:system-context-census": "node scripts/check-system-context-census.mjs --self-test && node scripts/check-system-context-census.mjs"
"check:system-context-census": "node scripts/check-system-context-census.mjs --self-test && node scripts/check-system-context-census.mjs",
"gen:platform-object-tenancy-census": "node scripts/platform-object-tenancy-census.mjs --write",
"check:platform-object-tenancy-census": "node scripts/check-platform-object-tenancy-census.mjs --self-test && node scripts/check-platform-object-tenancy-census.mjs"
},
"keywords": [
"objectstack",
Expand Down
39 changes: 27 additions & 12 deletions packages/objectql/src/tenancy/platform-object-tenancy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,33 @@
* measurement is the reason this file exists. `applySystemFields`
* (`registry.ts`) provisions the tenant COLUMN unconditionally: its existence
* was deliberately decoupled from whether tenancy is on, so that sudo writers
* can always stamp it. Measured on this tree by AST census of every
* `ObjectSchema.create` in `packages/`:
*
* - 84 platform-namespace objects are registered in this repository;
* - 25 resolve NO tenant field (24 `managedBy: 'better-auth'`, plus
* `sys_sso_provider`'s `tenancy.enabled: false`) and are already outside
* the machinery — they exit at `resolveTenantFieldName` returning null;
* - **59 carry a tenant column**, `sys_permission_set` — #8672's own example
* of a legitimately org-less object — among them.
*
* So a schema read admits 59 of 84 in one stroke, i.e. it replaces a wholesale
* exemption with a wholesale inclusion. The ruling's classification source is
* can always stamp it.
*
* ⛔ The census that measures this is NOT restated here. It is derived by
* `scripts/platform-object-tenancy-census.mjs`, committed as
* `scripts/platform-object-tenancy-census.json`, and held to the tree by
* `scripts/check-platform-object-tenancy-census.mjs` (#14957). Its PREDICATE is
* the half this paragraph used to leave out: an object is inside the machinery
* when `resolveTenantFieldName` answers non-null on the registered schema —
* after `applySystemFields`, because the injected column is what the engine
* sees, not what the author typed. The artefact records, per object, the
* declaration on its OWN schema that puts it outside.
*
* ⛔ Do not restate a count here "for reference". This paragraph carried three
* hand-written digits and a parenthetical explaining them, and each half failed
* in its own direction. The parenthetical mis-attributed the exclusion — it
* named an object as an addition to the set it was already in — while its
* arithmetic stayed right, which is why nothing caught it. The digits then went
* stale when an object opted out of the tenant column (`systemFields: { tenant:
* false }`, a third mechanism the parenthetical's taxonomy had no slot for), and
* CI was green throughout. A number in this comment has no second party; the
* artefact has a gate.
*
* The ARGUMENT survives that measurement, and no digit was load-bearing for it:
* the great majority of platform-namespace objects carry the tenant column —
* `sys_permission_set`, #8672's own example of a legitimately org-less object,
* among them — so a schema read admits nearly all of them in one stroke, i.e.
* it replaces a wholesale exemption with a wholesale inclusion. The ruling's classification source is
* "有列**且有写手填**" — the column AND a writer that fills it — and the second
* half is not a runtime fact. It is a fact about the CODE, established once by
* inventory and written down here.
Expand Down
Loading
Loading