Skip to content

feat(metadata-core, objectql): machine-readable provenance for injected system columns (#7865) - #8115

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-7865-external-anchor-provenance
Aug 12, 2026
Merged

feat(metadata-core, objectql): machine-readable provenance for injected system columns (#7865)#8115
os-zhuang merged 2 commits into
mainfrom
claude/issue-7865-external-anchor-provenance

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes#7865

What this implements — the 2026-08-12 maintainer ruling, direction B

applySystemFields keeps injecting the platform anchors into external (ADR-0015) objects — deliberately unchanged — and the anchors it registers without provisioning storage now carry a machine-readable provenance marker, so consumers ask one authoritative question ("is this column actually provisioned by the platform?") instead of each re-deriving "that column is not really there".

The marker is the ruling's licensed equivalent ("provisioned: false 或等价物"), spelled as an exported derivation in @objectstack/metadata-core (the package the definition tables already live in, per #6562's discipline), re-exported by @objectstack/objectql at the injection site:

  • platformProvisionsStorage(def)false exactly for ADR-0015 external objects; the one exported spelling of the predicate syncObjectSchema routes by.
  • resolveInjectedColumnProvenance(def, column)'author' | 'injected-provisioned' | 'injected-unprovisioned' | 'absent'. 'injected-unprovisioned' is the marker: the platform's own injected anchor with no storage behind it.
  • unprovisionedInjectedColumns(def) — the enumerable form.

Why the marker is an API and not a provisioned: false key in the data

Measured, not asserted — the literal data-key spelling cannot land without moving surfaces the ruling fenced off:

  1. FieldSchema is strictObject. An undeclared key on the injected defs reaches /meta (which serves the post-injection document) and stamps _diagnostics: { valid: false, unrecognized_keys } on every federated object — the exact meta: applySystemFields stamps indexed on organization_id — a key FieldSchema rejects by name, so every registry-backed object read answers _diagnostics: { valid: false } #6810 defect, closed once already. Declaring the key instead makes it authorable: a forgeable switch over the author's own tenant wall, the shape federated-phantom-anchors.ts (fix(plugin-security): Layer 0 no longer walls a federated object with a phantom organization_id (#7835) #7859) records as deliberately rejected.
  2. Two consumers read the anchor defs by exact key-count identity: plugin-security's Layer-0 guard (equalsShippedDef) and the [P3] Read decorations (_diagnostics, _draft) round-trip into persisted sys_metadata bodies #4326 round-trip strip (isInjectedDefinition). Any key added to the external-object anchors flips both from "the platform's anchor" to "the author's field" — for the guard that re-emits the phantom tenant predicate, resurrecting the measured zero-rows defect, and the guard is explicitly out of this PR's scope.
  3. The fence on the card: the marker must not change what any consumer accepts. This spelling changes zero document bytes — registered, served, stored — which is what makes the no-regression proofs below exact.

If the maintainer wants the data-key spelling regardless, that is the fork the card names, and this PR's evidence is the input to it.

Before / after — the card's own measurement, on a real boot

Real boot of the shipped showcase (@objectstack/verify harness, same as the #7835 measurement), packages/qa/dogfood/test/federated-anchor-provenance.dogfood.test.ts:

Before-picture (preserved — direction B keeps injecting):showcase_ext_customer (external.remoteName: "customers", remote table columns name, email, region, lifetime_value) still registers all 7 platform anchors: organization_id, created_at, created_by, updated_at, updated_by, owner_id, owning_business_unit_id — every one byte-identical to the shipped definition tables (pinned per anchor; no provisioned key in any registered or served field definition).

After-picture (the marker):

  • platformProvisionsStorage(showcase_ext_customer)false
  • unprovisionedInjectedColumns(showcase_ext_customer) → exactly the 7 anchors
  • resolveInjectedColumnProvenance(…, anchor)'injected-unprovisioned' for each of the 7; 'author' for the 4 declared remote columns
  • Local control showcase_project: 'injected-provisioned', empty unprovisioned list

Blast-radius surfaces — proved unchanged, one line each

  • /meta (post-injection document, meta: an overlay-backed object read OMITS the injected system columns a registry-backed read includes — the same endpoint answers two different field sets #6562 precedent): no serve-path file touched; on the real boot, GET /meta/object/showcase_ext_customer through the real HTTP stack answers 200 with the same field-name set as the registry, anchor defs byte-identical to the shipped tables, and no _diagnostics invalidity (dogfood case "/meta NO-REGRESSION", green). The /meta route itself (domain:cli territory) is unmodified.
  • Studio forms: they read the same served field sets; the served document is byte-identical (previous line), packages/spec is untouched entirely (no schema, no authorable key, no react-blocks/SDUI artifact moved), and objectui is untouched.
  • ADR-0087 declaration-parity ratchet: no authorable surface changed and no conversion/migration entry is needed — the changeset is minor, non-breaking, so no ADR-0087 disposition marker is required (node scripts/check-changeset-no-major.mjs green; check:changeset-gate-self-tests green). The ruling's "ADR-0087 entries ride this PR if the marker needs one" resolves to: it does not need one.

Consumer guards (#7833 engine, #7859 plugin-security, #7858 plugin-sharing): untouched, per the ruling's opportunistic-convergence clause. Behavioural parity with #7859's live guard is pinned on the real boot: marker 'injected-unprovisioned' ⇔ Layer 0 composes no organization_id predicate on the federated object; marker 'injected-provisioned' ⇔ the wall stands on the local control. An author-declared organization_id on a federated object answers 'author', never the marker, so convergence can never suppress a wall the author made real — the fail direction of any inexact match is 'author', toward enforcement.

#5378 / #6113 (author-time expression validation): the marker does not reach that surface in this PR. @objectstack/lint's package contract is "depends on @objectstack/spec, never on a runtime", and the marker needs the definition tables that live in @objectstack/metadata-core (the WHICH/WHAT split of #3786). The derivation's inputs are all document-declared keys, so a future card can sink it into spec if that surface needs it; noted in the dev report.

Tests and gates (local, real output in the dev report)

  • @objectstack/metadata-core test: 10 files / 162 passed (new: injected-column-provenance.test.ts — verdict matrix, security direction, fail direction, array shape, opt-outs, the fence pin on the def tables).
  • @objectstack/objectql test: 190 files / 3379 passed (extended injected-system-columns-parity.test.ts: marker ↔ live-injection parity over the full branch matrix × external, byte-identity of injected defs, author-declared anchor stays 'author').
  • @objectstack/dogfood: full suite 96 files / 630 passed; the two measurement files re-run verbose: 10/10 green.
  • Typecheck green: metadata-core (src + test tsconfig), objectql, dogfood.
  • Gates: check:durability-log-level ✓, check:engine-double-contract ✓ (169 pinned), check-engine-split-ratio ✓, check:test-source-alias ✓ (new metadata-core import aliased to source in dogfood's vitest config, per the gate's own prescription), check:query-options-erasure ✓ (67 sites, none new), check:type-check-debt --re-measure ✓ (35 entries, 1976 raw errors, none above its recorded number; --lower not run, per dispatch), check:nul-bytes ✓, ESLint on all changed files ✓, check:changeset-no-major ✓.

Serial-constraint note: registry.ts changes are confined to the re-export block (~line 327) and applySystemFields (~line 364) — uninstallPackage (#7970's seam) is untouched.


Generated by Claude Code

…ed system columns (#7865)
Direction B per the 2026-08-12 maintainer ruling: applySystemFields keeps
injecting the platform anchors into external (ADR-0015) objects, and the
anchors it registers without provisioning storage now carry a machine-readable
provenance marker — spelled as an exported derivation
(resolveInjectedColumnProvenance / unprovisionedInjectedColumns /
platformProvisionsStorage in @objectstack/metadata-core, re-exported by
@objectstack/objectql) rather than a provisioned key on the field defs, so no
document byte changes anywhere: FieldSchema is strict (a data key would stamp
_diagnostics invalidity on every served federated object, or become an
authorable forgeable wall-off switch), and the #7859 Layer-0 guard plus the
#4326 round-trip strip read the anchor defs by exact key-count identity.
The three existing consumer guards (#7833 engine, #7859 plugin-security,
#7858 plugin-sharing) are deliberately untouched — opportunistic convergence
per the ruling. Proofs: unit matrix in metadata-core, marker-vs-live-injection
parity in objectql, and a real showcase boot pinning the before-picture
(7 anchors on showcase_ext_customer, byte-identical), the marker verdicts,
behavioural parity with the live Layer-0 guard, and /meta serving an unchanged,
valid post-injection document.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014C8pAprWdmtecFsEprZax4
@vercel

vercelBot commented Aug 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 12, 2026 4:05pm

Request Review

@github-actionsgithub-actionsBot added size/l documentation Improvements or additions to documentation dependencies Pull requests that update a dependency file tests labels Aug 12, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/metadata-core, @objectstack/objectql, @objectstack/dogfood.

17 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/concepts/metadata-lifecycle.mdx(via @objectstack/metadata-core, @objectstack/objectql)
  • content/docs/data-modeling/formulas.mdx(via packages/objectql)
  • content/docs/deployment/migration-from-objectql.mdx(via @objectstack/objectql)
  • content/docs/deployment/vercel.mdx(via @objectstack/objectql)
  • content/docs/kernel/contracts/data-engine.mdx(via @objectstack/objectql)
  • content/docs/kernel/runtime-services/examples.mdx(via packages/objectql)
  • content/docs/kernel/services-checklist.mdx(via @objectstack/objectql)
  • content/docs/kernel/services.mdx(via @objectstack/objectql)
  • content/docs/permissions/authentication.mdx(via @objectstack/objectql)
  • content/docs/permissions/authorization.mdx(via packages/qa/dogfood)
  • content/docs/permissions/delegated-administration.mdx(via packages/qa/dogfood)
  • content/docs/permissions/system-context.mdx(via packages/objectql)
  • content/docs/plugins/index.mdx(via @objectstack/objectql)
  • content/docs/plugins/packages.mdx(via @objectstack/metadata-core, @objectstack/objectql)
  • content/docs/protocol/kernel/index.mdx(via @objectstack/objectql)
  • content/docs/protocol/objectql/query-syntax.mdx(via packages/objectql)
  • content/docs/protocol/objectql/state-machine.mdx(via @objectstack/objectql)

2 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx(via @objectstack/objectql)
  • content/docs/releases/v12.mdx(via @objectstack/metadata-core)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang marked this pull request as ready for review August 12, 2026 16:47
@os-zhuang
os-zhuang added this pull request to the merge queueAug 12, 2026
Merged via the queue into main with commit 3670cf9Aug 12, 2026
28 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-7865-external-anchor-provenance branch August 12, 2026 17:05
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependenciesPull requests that update a dependency filedocumentationImprovements or additions to documentationsize/lteststooling

Projects

None yet

2 participants

@os-zhuang@claude