Uh oh!
There was an error while loading. Please reload this page.
feat(platform-objects): declare sys_metadata_activation, the ADR-0126 §4 activation ledger - #12185
Conversation
…on ledger Implements ADR-0126 §4 (D2): one data-plane platform object for the disable+clone family, declared beside its siblings so it needs zero packages/spec surface. Schema is exactly §4's five columns plus the primary key. The designation columns an earlier draft carried (replaced_by, cloned_from) are absent per amendment ruling 2 — there is no recorded linkage between a clone and its base — and the pin test asserts the column set by equality so re-growing the linkage is loud. Row identity is (metadata_type, name, organization_id NULL-collapsed), spelled as a declared index with unique: 'organization' (ADR-0120 D1). A hand-written composite naming organization_id verbatim would be NULL-distinct, and this line's organization_id is NULL on every row by construction, so that spelling would enforce nothing (#5030). The 'organization' arm makes the driver prepend COALESCE(organization_id, '__global__') at registration (ADR-0120 D3). No lifecycle block on purpose: a row is durable configuration, not telemetry, and reaping one would silently re-arm a disabled artifact. No consumers in this leg — the enable/disable actions and the per-runtime consult points are separate legs. An empty ledger changes nothing.
📓 Docs Drift CheckThis PR changes 2 package(s): 52 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 5 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 126 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 0d3f2c427031d6af1da8c600b73d6434a3ef97a4 && git checkout 0d3f2c427031d6af1da8c600b73d6434a3ef97a4
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin c02fa2214049789405ff93316fdcae6e3df38ab3 4ea01e5d9ae1b7e22500f9b01ca5620343d98245 && git checkout -B drift-repro c02fa2214049789405ff93316fdcae6e3df38ab3 && git merge --no-ff 4ea01e5d9ae1b7e22500f9b01ca5620343d98245
node scripts/docs-audit/affected-docs.mjs --json c02fa2214049789405ff93316fdcae6e3df38ab3
|
os-support-ai
commented
Aug 25, 2026
CI note: "Type Check · consumer gates" is red here, and it is red on the base too — the base commit Generated by Claude Code |
…me census The platform-object name census is a curated set of REAL names rather than a sys_-prefix pattern, so that a cross-reference check can tell a real platform object from a fictional platform-prefixed one. Its module contract states the obligation outright: adding an object to a platform package means adding its name here, and the owning package's conformance pin fails otherwise. Caught by CI (Test Core 2/6, platform-object-names.test.ts:115 — registry group "platform-objects" is out of date: expected 38 names, received 39). The local gate derivation could not reach it: the change set at the time touched no packages/spec path, so nothing named the spec-owned pin that scans packages/**. This is a one-name roster registration, not protocol or schema surface — the ledger stays an ordinary platform object with no zod/contract surface, exactly as ADR-0126 §4 requires. User-visible effect: isPlatformProvidedObjectName now answers true for the name, so lint stops reading it as a typo. The changeset gains @objectstack/spec accordingly.
…-metadata-activation-ledger Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KWRU3s15AJz7PGW7a7wdCh
Uh oh!
There was an error while loading. Please reload this page.
Closes#12155
Part of #12150 (Epic: ADR-0126 implementation, v17 line)
L1 of the ADR-0126 program: declare the activation ledger. Declaration only — the enable/disable actions that write it and the per-runtime consult points that read it are separate legs (L2/L3), and nothing in the tree reads the object yet.
The diff (5 files)
packages/platform-objects/src/system/sys-metadata-activation.object.tspackages/platform-objects/src/system/sys-metadata-activation.object.test.tspackages/platform-objects/src/system/index.tspackages/spec/src/system/constants/platform-object-names.tsplatform-objectscensus group.changeset/sys-metadata-activation-ledger.mdminoron@objectstack/platform-objectsand@objectstack/specZero
packages/specschema/contract surface, as the card requires — the ledger is an ordinary platform object, not a metadata type, so it carries no zod or protocol surface of its own (ADR-0126 §4).The one
packages/specfile touched is the mechanical platform-object name census every new platform object owes. That census is a curated set of real names rather than asys_-prefix pattern — the whole point is that a cross-reference check can tellsys_user(real) from a fictional platform-prefixed name — and its module contract states the obligation outright: "adding an object to a platform package means adding its name here. The owning package's conformance test fails otherwise." Its user-visible effect is thatisPlatformProvidedObjectName('sys_metadata_activation')now answerstrue, so lint stops reading a reference to the ledger as a typo.This was missed in the first push and caught by CI (Test Core 2/6,
platform-object-names.test.ts:115— registry group "platform-objects" is out of date: expected 38 names, received 39). Recorded rather than quietly amended: my local gate derivation ran over the change set at the time, which touched nopackages/specpath, so it never named the spec-owned conformance pin that scanspackages/**. A gate owned by a package your diff does not touch is exactly the shape a path-derived local run cannot reach.Schema — exactly ADR-0126 §4
metadata_type·name·package_id·organization_id(nullable, reserved — NULL on this entire line; no writer sets it in any leg here) ·active, plus the primary keyidthat every data-plane sibling declares and the driver provisions unconditionally.The designation columns an earlier ADR draft carried (
replaced_by,cloned_from) are absent per amendment ruling 2 — there is no recorded linkage between a clone and its base, matching the landed #11513 posture. The pin test asserts the column set by equality, not membership, and additionally names both removed columns, so a reader who deletes the equality assertion still trips the named one.Why the uniqueness is spelled
unique: 'organization'Row identity is
(metadata_type, name, organization_id NULL-collapsed). That is declared as:Both obvious alternatives are wrong here, and the test pins against each:
unique: trueon a declared index is the positional spelling of'global'(ADR-0120 D1) — installation-wide over exactly the listed columns — and is already warned by lintunique/unscoped-declared-indexin 17.x, rejected at protocol 18.['metadata_type', 'name', 'organization_id']composite is NULL-DISTINCT in SQL, and this line'sorganization_idis NULL on every row by construction — so that index would enforce nothing at all (driver-sql: 单租户栈上 organization_id 恒为 NULL,#3696 的 (tenant, col) 复合 UNIQUE 因 NULL-distinct 而完全不生效 —— 字段级 unique: true 静默零约束 #5030, measured), and one artifact could carry two contradictoryactiverows.'organization'is the arm that closes exactly that hole: the driver prependsCOALESCE(organization_id, '__global__')at registration (ADR-0120 D3), which is the "NULL-collapsed" of the §4 sentence.No
lifecycleblock, deliberatelyThe absent block is the back-compat
recordclass. Unlike the telemetry siblingssys_flow_dispatch/sys_automation_run, a row here is durable configuration — reaping one would silently re-arm an artifact an administrator disabled. That would be a data-loss bug wearing a tuning knob's clothes, so the omission is pinned by the test rather than left to be "fixed" later.Acceptance: "an empty ledger changes nothing"
Pinned as five separate facts rather than asserted in prose — no seed rows (no seeding key exists on the object schema),
activedefaults totrueso absence-of-row and row-without-flag agree, nolifecycle(the Reaper does no work), no generic write door (apiMethods: ['get', 'list'], engine-owned per ADR-0103), and no shipped app or nav contribution names the object (so a stock boot's UI is unchanged).Verification
Gate families derived mechanically from the real change set (
scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack), not recalled — and re-derived after the spec file joined the diff, which pulled 9 further families (check:liveness,check:empty-state,check:strictness-ledger,check:variant-docs,check:merge-driver,check:spec-parsed-alias,check:doc-formula-expressions,check-ci-filter-parity,check-dev-prereqs).At the current HEAD
e539438a— 19/19 green, each read from the gate's own verdict line with the exit code captured before any pipe:spec census test·platform-objects test·platform-objects typecheck·check:liveness·check:empty-state·check:strictness-ledger·check:variant-docs·check:merge-driver·check:spec-parsed-alias·check-ci-filter-parity·check:cross-package-test-inputs·check:changeset-gate-self-tests·check:objectui-changeset·check-adr-0087-registration·check-changeset-no-major·check-empty-changeset·check:published-files·check:type-check-coverage·check:nul-bytes.The CI-named failure is fixed at source:
platform-object-names.test.tsnow reportsTest Files 1 passed (1) · Tests 7 passed (7), including the very assertion that was red — "registers exactly the objects each package declares".The sweep one commit earlier (
99590074) additionally rancheck:slot-lookup,check:test-source-alias,check:type-source-resolution,check-plugin-teardown-shape,release-rehearsal-clone --self-test,check:engine-double-contract,check:where-matcher,check:query-options-erasure,check:i18nandcheck:i18n-stale-fill— all green, and untouched by the one-line census edit.Two gates are recorded as not measured rather than as verdicts, because each said so itself.
check:i18nrefuses without a built CLI ("PREREQUISITE NOT MET … Nothing was checked"); after building@objectstack/cliit renders a real verdict —check-i18n-bundles: OK (9 package(s) — all bundles in sync, no undeclared authoring keys).check:dev-prereqsreports "The workspace is not built — 1 unmet precondition, not a list of problems", naming 12 packages whosedist/this targeted build never produced; its remedy is a fullpnpm build, which CI does. Neither is a defect in this diff, and neither is claimed as green.check:i18nneeded its prerequisite built first (@objectstack/cli); its first run reported "PREREQUISITE NOT MET — nothing was checked", which is not a verdict. After building the CLI it renders a real one:check-i18n-bundles: OK (9 package(s) — all bundles in sync, no undeclared authoring keys). The new object is not in the extract config, so it emits nothing into the bundles and thebundle-ownershipguard stays balanced.Package suite:
Test Files 31 passed (31) · Tests 490 passed (490). The new file was confirmed to actually execute — the package'stestscript carries--passWithNoTests, so a zero-match run would read green:Test Files 1 passed (1) · Tests 15 passed (15)under--reporter=verbose, with all 15 named.Reverse-verification (the pin is not vacuous). The mutated module is imported relatively by the test, so vitest resolves it from source — no dependency
exportshop and no dist, so no rebuild leg applies. Each mutation was confirmed on disk by an anchored count before its reading was accepted, and restore was trapped onEXIT INT TERM:replaced_byreplaced_by: Field0 → 1unique: trueBoth matched the direction predicted before the run.
Premise note
The card names
sys_metadata_history/sys_automation_run/sys_flow_dispatchas siblings to study "beside" — worth recording that onmainthose three no longer share a package:sys_metadata_historymoved to@objectstack/metadata-core, andsys_automation_run/sys_flow_dispatchlive inservice-automation.packages/platform-objects/src/metadata/is now a back-compat re-export shim with nothing declared in it. The deliverable location was unambiguous regardless (packages/platform-objects, per both the card and ADR-0126 §4), so this landed insrc/system/— the package's home for cross-cutting system objects — and the declaration idiom was matched against all three siblings plus the in-packagesys_setting.Generated by Claude Code
Generated by Claude Code