Observation-class finding, filed per Prime Directive #10 while implementing #6725 (PR #7211). Unassigned, deliberately not queued. This is a test-coverage gap, not a defect in shipped behaviour — filed separately rather than folded into #6725 because the fix is a conformance suite, not a line of engine code.
The fact (read on origin/main @ 55da611)
IMetadataService.register and .get are the contract's first two CRUD members, and the round-trip between them is exercised in exactly one place:
packages/spec/src/contracts/metadata-service.test.ts — "should register and retrieve metadata items asynchronously":
const store = new Map<string, Map<string, unknown>>();
const service: IMetadataService = {
register: async (type, name, data) => { …store.get(type)!.set(name, data); },
get: async (type, name) => store.get(type)?.get(name),
…
};
await service.register('object', 'account', objectDef);
expect(await service.get('object', 'account')).toEqual(objectDef);
expect(await service.getObject('account')).toEqual(objectDef);
Every subject in that file is an inline literal written in the test itself. The suite asserts that a Map behaves like a Map. It is a type-conformance file — which is the right thing for packages/spec to host, since the contract has no runtime and spec is the dependency root — but it means the round-trip is pinned against no shipped implementation at all.
The cross-implementation pin that does exist,
packages/objectql/src/metadata-service-getobject-equivalence.test.ts (#6745), covers a different proposition — getObject(name) ≡ get('object', name) — and seeds every subject through registry.registerObject / manager.register, explicitly not through the facade's own write. Its header says so, and says why: seeding the facade the other way produced undefined on both members.
So: no test anywhere asserts that writing through an implementation's register makes the item readable through that same implementation's get.
Why this is worth a card
That is precisely the hole #6725 fell through. MetadataFacade.register('object', …) wrote into a map none of its own reads consult; every read answered undefined; the full packages/objectql suite (167 files, 2917 tests) stayed green, and so did all 64 lint.yml gates. A shipped, exported implementation of the platform's central metadata contract could not perform its own most basic round-trip, and nothing in the repo was positioned to notice.
packages/objectql is again the only package that can see all three implementations at once — it depends on @objectstack/metadata (MetadataManager) and @objectstack/core (createMemoryMetadata) and owns MetadataFacade — which is exactly the argument #6745's header already makes for living there.
Shape worth pricing (no recommendation forced)
A describe.each over the same implementation table #6745 builds, asserting the CRUD contract per implementation and per metadata type: register → get / exists / list / listNames; unregister → all four answer absent; re-register replaces rather than duplicates. At minimum one object type and one non-object type, since the object type is special-cased on the read side of SchemaRegistry and the generic types are not — the asymmetry that produced #6725.
Two things to decide before writing it:
Refs #6725, PR #7211, #6745, #6505 / PR #6723.
Observation-class finding, filed per Prime Directive #10 while implementing #6725 (PR #7211). Unassigned, deliberately not queued. This is a test-coverage gap, not a defect in shipped behaviour — filed separately rather than folded into #6725 because the fix is a conformance suite, not a line of engine code.
The fact (read on
origin/main@55da611)IMetadataService.registerand.getare the contract's first two CRUD members, and the round-trip between them is exercised in exactly one place:packages/spec/src/contracts/metadata-service.test.ts— "should register and retrieve metadata items asynchronously":Every subject in that file is an inline literal written in the test itself. The suite asserts that a
Mapbehaves like aMap. It is a type-conformance file — which is the right thing forpackages/specto host, since the contract has no runtime and spec is the dependency root — but it means the round-trip is pinned against no shipped implementation at all.The cross-implementation pin that does exist,
packages/objectql/src/metadata-service-getobject-equivalence.test.ts(#6745), covers a different proposition —getObject(name)≡get('object', name)— and seeds every subject throughregistry.registerObject/manager.register, explicitly not through the facade's own write. Its header says so, and says why: seeding the facade the other way producedundefinedon both members.So: no test anywhere asserts that writing through an implementation's
registermakes the item readable through that same implementation'sget.Why this is worth a card
That is precisely the hole #6725 fell through.
MetadataFacade.register('object', …)wrote into a map none of its own reads consult; every read answeredundefined; the fullpackages/objectqlsuite (167 files, 2917 tests) stayed green, and so did all 64lint.ymlgates. A shipped, exported implementation of the platform's central metadata contract could not perform its own most basic round-trip, and nothing in the repo was positioned to notice.packages/objectqlis again the only package that can see all three implementations at once — it depends on@objectstack/metadata(MetadataManager) and@objectstack/core(createMemoryMetadata) and ownsMetadataFacade— which is exactly the argument #6745's header already makes for living there.Shape worth pricing (no recommendation forced)
A
describe.eachover the same implementation table #6745 builds, asserting the CRUD contract per implementation and per metadata type:register→get/exists/list/listNames;unregister→ all four answer absent; re-registerreplaces rather than duplicates. At minimum one object type and one non-object type, since theobjecttype is special-cased on the read side ofSchemaRegistryand the generic types are not — the asymmetry that produced #6725.Two things to decide before writing it:
getObject(n)=get('object', n)equivalence across all three IMetadataService implementations #6745's file or start a sibling. Sharing risks turning one pin into a general-purpose suite whose failures no longer name one proposition; a sibling duplicates the fixture.MetadataManagerhas loader fallback,MetadataFacadeanswers the runtime-effective object rather than the stored document (IMetadataService.getObject has no declared relationship to get('object', name), and its two implementations disagree #6505 / PR docs(spec): declare whatIMetadataService.getObjectanswers with (#6505) #6723) — sotoEqual(input)is the wrong assertion for at least one subject, and picking the right one per member is most of the work.Refs #6725, PR #7211, #6745, #6505 / PR #6723.