Uh oh!
There was an error while loading. Please reload this page.
fix(platform-objects,core): sys_metadata_activation ships tenant-less — drop the reserved organization_id (#15024) - #15155
Conversation
Drop the reserved organization column from the activation ledger: the object opts out of tenant-column injection, the declared unique index states the 'global' scope it actually materializes, and the store's NULL-filter and org-row skip go with the column they guarded. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ence Rewrites the pins that asserted the reserved column and the org-row skip so they pin the column's ABSENCE instead of being deleted, updates the row-shape docblocks in both consumers and the runtime activation doors, corrects the two pending changesets that would otherwise describe the column in 17.3's release notes, and adds this change's changeset. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 5 package(s): 26 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 4 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 47 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 2a4a244161196690cbcdee91c4e80788bc43ef6a && git checkout 2a4a244161196690cbcdee91c4e80788bc43ef6a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 5c584231a1bcefd15b62f91992b3e2f4ca00bbba 36695cd33ac9a012a3714956dbdb3e1907e1002b && git checkout -B drift-repro 5c584231a1bcefd15b62f91992b3e2f4ca00bbba && git merge --no-ff 36695cd33ac9a012a3714956dbdb3e1907e1002b
node scripts/docs-audit/affected-docs.mjs --json 5c584231a1bcefd15b62f91992b3e2f4ca00bbba
|
hotlong
commented
Sep 4, 2026
Patch round 1 — one red, pure line rot. 🤖 Generated with Claude Code |
…lock shift The activation-gate docblock correction in this branch is one line shorter than the text it replaced, so both `ec.isSystem` elevation reads moved up by one line. Row 56's anchors on the system-context census page still pointed at the old lines, which the census gate reports from both directions at once: [site-without-a-row] for :138 and :189, and [anchor-is-not-a-read-site] for the stale :139 and :190. Pure line rot, repaired by the gate's own `--fix`. Only the two anchor numbers move; no elevation behaviour text changes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Uh oh!
There was an error while loading. Please reload this page.
Fixes#15024
The ADR-0126 activation ledger records that this environment switched a packaged
artifact off. That is deployment-level state, owned by no organization — so
sys_metadata_activationships with no tenant column at all.It briefly declared one: an
organization_idmarked "RESERVED", nullable, and written bynobody, held for a per-organization dimension ADR-0126 §5 pre-charted. A reserved nullable
tenant column is exactly the shape the total-organization-ownership record proposed in
PR #14976 rules out, and this one had no reader either. This is a plain removal, not a
migration — measured below, not assumed.
⛔ Nothing in the ADR-0126 machinery is reverted or weakened. The packaged-flow /
packaged-action disable + clone family ships in 17.3 as it stands; the operator gate under
walled postures and both consult seams are untouched and stay pinned.
What changed
systemFields: { tenant: false }and the field deleted. Both halves are needed — the tenant anchor is INJECTED at registration (resolveInjectedSystemColumns→applySystemFields), so deleting the field alone would have left the column exactly where it was.unique: 'organization'→unique: 'global'over(metadata_type, name).list()drops theorganization_id == nullfilter and the org-row skip;setActivetakes the single row its keyed read returns.ObjectStoreFlowActivationStore/ObjectStoreActionActivationStoreinherit it.normalizeDeclaredIndexprepends the NULL-safe tenant key part onlyif (idx.unique === 'organization' && tenantField), and with no tenant columncomputeTenantFieldresolves tonull— so'organization'already degraded to exactlythese two columns. The DDL is identical; what changes is that the declaration now states the
boundary it actually gets. Still explicit rather than bare
unique: true, which lintunique/unscoped-declared-indexwarns on and protocol 18 rejects.systemFields.tenantand nottenancy.enabled. Both suppress the column, andboth reach
plugin-security'stenancyDisabled— which is required, not incidental: aLayer 0 wall composing an equality on a column the table does not have denies every row.
They differ in what else they reach: only
tenancy.enabled: falsetrips the spec'sisTenancyDisabled(driver native scoping, the sticky per-table opt-out record), which thistable does not need, because with no column
computeTenantFieldalready returnsnullonits own. The sibling
sys_sso_providerusestenancy.enabled: falsefor the oppositeshape — a table that KEEPS its tenant column and needs the wall over it stood down.
Verification
git rev-parse --short HEAD=35ed1e107— every result below is from that tree, andthe working tree is clean at it.
Acceptance grep returns nothing, on both implementation files:
Positive control: the same pattern returns 3 hits on
sys-sso-provider.object.ts, a tablethat does carry the column. To reach zero while keeping the reasoning, both files name the
tenant column in words rather than as the literal token — the absence is the contract, so
it should be checkable by the one-line grep an auditor reaches for.
column's absence requires naming what is absent, and the card's own Scope §4 asks for exactly
those pins. The zero holds where the column would live.
The column is gone from the emitted schema — read, not inferred. Two independent levels:
resolveInjectedSystemColumns(SysMetadataActivation)reportstenant: falseand anamesset without
organization_id— the spec derivation that decides whether the columnexists, and the one
applySystemFieldsconsumes. Anti-vacuity: the same set still containsid.packaged-activation-ledger-reach.dogfood.test.ts, 11/11), theactivation row returned by the driver's own SELECT has no
organization_idkey.Anti-vacuity: the same key set is asserted to contain
id,metadata_type,name,active.expect(row.organization_id ?? null).toBeNull().It could not be carried forward: once the column is gone the property is
undefined,undefined ?? nullisnull, and it would have passed while measuring nothing — green forprecisely the reason it should have gone red. It is inverted into a key-set assertion.
Reverse verification (ablation). Removing the
systemFields: { tenant: false }line turnsthe new absence pin red —
AssertionError: expected true to be false, 1 failed / 14passed. Mutation confirmed on disk before the run (anchor occurrences 1 → 0,
hash-objectchanged); restore proven after (
git diff HEADempty, and the working blob hash equals theHEAD blob hash,
3050a5bc…on both sides), under atrap … EXIT INT TERMwith absolutepaths. No rebuild leg was required and none is claimed: vitest resolves that file from
source via a same-package relative import, not through
exports→dist.Never shipped — measured.
git merge-base --is-ancestor: this fix is not an ancestorof
@objectstack/account@17.2.0; positive control,@objectstack/account@17.2.0~1is, sothe predicate can answer YES. And the object's own adding commit (
428f9b24a, 2026-08-25) isnot an ancestor of the tag (2026-08-23) — the table postdates 17.2.0, so no released
version ever carried the column.
Suites (all green, at
35ed1e107):@objectstack/objectql@objectstack/runtime@objectstack/service-automation@objectstack/core@objectstack/platform-objects@objectstack/dogfood(ledger reach)typecheckgreen for core / platform-objects / objectql / runtime — andcheck:test-typecheckcompiled the test layer in each, so the rewritten pins weregenuinely type-checked rather than skipped by a
*.test.tsexclusion.The seams the card says must not move, verified unchanged (A5): no consult seam reads the
column in code — every hit outside the two implementation files was docblock prose. The
isolated-posture operator gate passes unchanged, includingREFUSES a tenant org admin, loudly, and never writes the rowandALLOWS the platform operator; so does theexecute()seam, includingrefuses a disabled record-change flow with FLOW_DISABLED, and re-enabling restores firingand
refuses on the SUBFLOW entry path.Gates. Families derived from the real diff with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(48 families).47 measured green, each read from its own verdict line with the exit code captured before
any pipe. Two needed a full
pnpm buildfirst and were re-run after it rather than reportedon their prerequisite failure:
check:dual-build-cjs-loads—✓ … 102 published require entry point(s) across 66 package(s) load; 610 emitted CommonJS file(s) parsecheck:published-readme-exports—✓ … 60 published document(s) across 79 workspace package(s)@objectstack/spec check:react-declaration-parity—MANIFEST is not set — there is no registry side to compare against. This gate did NOT run.It needs ansdui.manifest.jsondumped from a sibling
objectuicheckout. Environmental, and unrelated to this diff (itentered the family list only because the dogfood test path matches a broad
packages/qa/dogfood/**CI trigger). CI runs it with the manifest available.scripts/pm/os-verify-lock.shran in UNLOCKED (declared) mode on thishost —
no usable flock on this host, so the shared verify lock was NEVER taken and NOTHING was serialized. Every heavy command above went through the entry point, but none of it wasserialized against other agents in this container.
dispatch-gates.mjsreported a STALE TREE — this branch is 2 commitsbehind
origin/mainand one file it derives from (scripts/check-type-check-coverage.mjs)changed in that range. That gate is not in the derived family list, and I did not rebase.
Also in this diff, and why
.changeset/sys-metadata-activation-ledger.mdand.changeset/ledger-convergence-registration-and-one-store.mdare unreleased and describethe reserved column and the org-row skip as shipping behaviour. Left alone they would
announce a column 17.3 does not have, in the release notes for the very release this card
exists to get right.
action-activation.ts,flow-activation-store.ts,service-automation/engine.ts,runtime/domains/actions.tsandruntime/domains/activation-gate.tsdescribed the removed column; each is a one-passagemechanical correction in the same defect class.
SKIPS a row carrying an organization_idtests, which pinned the behaviour being deliberately removed and wouldotherwise have gone red for the right reason with no replacement.
Out of scope, filed
platform-core.activation-ledger-row-contractiswritten around the removed column; its step 7 instructs an
INSERTinto a column that nolonger exists, so that leg cannot run. Not fixed here because the replacement is a judgement
about what the checklist should assert, not a mechanical edit.
pnpm check:platform-checklistreports 4UNCLASSIFIEDcoverage problems (
batch_endpoints,crud_endpoints,metadata_endpoints,route_generation). Verified byte-identical atorigin/main(fcc42e6c1) in a throwawayworktree, so it is not from this diff. That gate is deliberately not wired into CI.
🤖 Generated with Claude Code