Skip to content

test(spec,objectql): pin the IMetadataService registerget round-trip across every shipped implementation (#7223) - #7371

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-7223-metadata-service-roundtrip-pins
Aug 10, 2026
Merged

test(spec,objectql): pin the IMetadataService registerget round-trip across every shipped implementation (#7223)#7371
os-zhuang merged 1 commit into
mainfrom
claude/issue-7223-metadata-service-roundtrip-pins

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes #7223.

The gap

register(type, name, data) and get(type, name) are the contract's first two CRUD members, and the round-trip between them was exercised in exactly one place — packages/spec/src/contracts/metadata-service.test.ts, against a hand-rolled Map-of-Maps double written inside the test itself. No shipped implementation was held to it.

That is the hole #6725 fell through: MetadataFacade.register('object', …) wrote into a map none of its own reads consulted, every read answered undefined, and the full packages/objectql suite plus all 64 lint.yml gates stayed green while a shipped, exported implementation of the platform's central metadata contract could not perform its own most basic round-trip.

The shape

One table, a thin driver per implementation — the same shape data/filter-logic-conformance.ts already uses for filter backends.

METADATA_ROUNDTRIP_CASES (@objectstack/spec/contracts, new file) — 15 cases, each a short sequence of writes followed by exactly one read: plain round-trip on an object-typed and a non-object-typed write, the miss shape on both, re-registration, type scoping in both directions, name case sensitivity (exact hit + lowercased miss), the data.name-vs-argument keying question, the plural objects spelling, a primitive data value, and an after-unregister control.

Two drivers run it:

Driver Subjects
packages/spec/src/contracts/metadata-service-roundtrip-conformance.test.ts the contract's own reference double
packages/objectql/src/metadata-service-roundtrip-conformance.test.ts MetadataManager (registry only), MetadataManager (writable datasource: loader), createMemoryMetadata, MetadataFacade

packages/objectql hosts the shipped-implementation half because it is the only package that can see all three at once — the same argument metadata-service-getobject-equivalence.test.ts (#6745) already makes for living there. packages/spec cannot host it: the contract has no runtime and spec is the dependency root.

The pre-existing Map double in metadata-service.test.ts is untouched. It pins the contract's type surface and its own inline round-trip, independent of any implementation; the new spec-side driver pins the table's reference answers so a typo in expected cannot silently redefine conformance for every subject at once.

MetadataManager appears twice because its register writes the in-memory registry and persists to every writable datasource: loader — a loader-less subject never executes the second half.

Divergences — pinned, not resolved

Three cases get different answers from MetadataFacade than from the other implementations and the reference double. No shipped behaviour changes in this PR. Each answer is pinned as measured under a // DIVERGENCE marker; which answer is correct is a separate ruling, filed as its own card.

Case MetadataManager / createMemoryMetadata / reference MetadataFacade
key-is-the-name-argument-object / -nonobject keyed by the name argument — readable back keyed by data.nameget undefined, exists false, listNames reports the other spelling
plural-objects-type-is-its-own-store objects and object are two stores — invisible under the singular plural aliased to singular — visible through get('object', n)
primitive-data-roundtrips value stored against the key, handed straight back silently droppedregister accepts, no member reads it back

The first is the most consequential: register(t, n, d)get(t, n), the exact proposition this card is about, does not hold on MetadataFacade whenever d.name !== n. The contract TSDoc names the parameter on both members and says nothing about data.name, so nothing in-tree currently rules which is right.

Anti-vacuity

Verified by re-introducing the #6725 split locally (dropping the contributor write from MetadataFacade.registerObjectBothPlaces): four rows go red, including the plain object round-trip. Reverted before commit. Dropping any single divergence override also goes red, and two wiring tests guard the override map from both directions — a stale override key, and a case no subject is held to.

MetadataFacade is asserted with a recursive-subset match rather than exact equality, because it answers the runtime-effective object (system fields injected by SchemaRegistry) rather than the stored document — exactly as #7223 predicted. That weaker match is scoped to the one subject that needs it via a declared documentFidelity; every other subject is held to exact equality, and all four get the same key/visibility assertions.

Changeset

Non-empty, @objectstack/spec: patch. This is not the tests-only skip-changeset case: the PR adds 5 public exports to @objectstack/spec (METADATA_ROUNDTRIP_CASES + 4 types), which third-party implementors can import to check their own backend — the FILTER_LOGIC_CASES precedent, which is likewise on spec's public surface. check:api-surface recorded 0 breaking, 5 added; api-surface/contracts.json and export-origins/contracts.json are regenerated, each new name resolving to one origin in the new file (no re-homing, no dual source). Nothing breaking ⇒ no ADR-0087.

⚠️ Cross-seat

This PR touches packages/spec/src/contracts/** — one new file plus four lines in contracts/index.ts. Flagged for the #6017 cross-seat declaration.

Refs #7223, #6725, PR #7211, #6745, #6505 / PR #6723.

🤖 Generated with Claude Code

https://claude.ai/code/session_0193R6tMZqgrdFrCSnaogFc4


Generated by Claude Code

…ip across every shipped implementation (#7223)

`register(type, name, data)` and `get(type, name)` are the contract's first
two CRUD members, and the round-trip between them was exercised in exactly
one place — `packages/spec/src/contracts/metadata-service.test.ts`, against a
hand-rolled `Map`-of-`Map`s double written inside the test itself. No shipped
implementation was held to it, which is the hole #6725 fell through: a shipped,
exported `IMetadataService` could not perform its own most basic round-trip
while the full objectql suite and all 64 `lint.yml` gates stayed green.

Adds `METADATA_ROUNDTRIP_CASES` (`@objectstack/spec/contracts`) — 15 cases,
one table, a thin driver per implementation, the shape
`data/filter-logic-conformance.ts` already uses for filter backends — plus the
two drivers that run it:

  - the contract's own reference double, in `packages/spec` (the dependency
    root, which can see no implementation);
  - every implementation this repo ships, in `packages/objectql` (the only
    package that can see all three at once): `MetadataManager` with and
    without a writable loader, `createMemoryMetadata`, `MetadataFacade`.

The pre-existing Map double in `metadata-service.test.ts` is untouched — it
pins the contract's type surface and its own inline round-trip, independent
of any implementation.

No shipped behaviour changes. Three cases get different answers from
`MetadataFacade` than from the other implementations and the reference double;
each is pinned as measured under a `// DIVERGENCE` marker and filed as its own
card rather than reconciled here.

Verified the suite is not vacuous by re-introducing the #6725 split locally
(dropping the contributor write from `MetadataFacade.registerObjectBothPlaces`):
four rows go red, including the plain object round-trip.

Refs #7223, #6725, PR #7211, #6745.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0193R6tMZqgrdFrCSnaogFc4
@vercel

vercel Bot commented Aug 10, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 10, 2026 8:05am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

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

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/tenancy-modes.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/permissions/system-context.mdx (via packages/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/apps.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/field-grouping-and-order.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

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

  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Nothing pins register(type, name, d)get(type, name) across the shipped IMetadataService implementations — the hole #6725 fell through

2 participants