Uh oh!
There was an error while loading. Please reload this page.
fix(core,metadata,objectql): enforce the #7378 three-cell IMetadataService register ruling in every shipped implementation - #8349
Conversation
…ling in every shipped IMetadataService implementation Maintainer ruling 2026-08-12 (#7378): row 1 — a data.name disagreeing with the name argument is refused loudly with a locating error; row 2 — type stores are keyed on the canonical type, converging with check:meta-type-normalized's enforced plural->singular direction; row 3 — a non-object data is refused (throw), never accepted-and-dropped and never coerced into storability. The shared guard and fold live in @objectstack/core (metadata-service-contract.ts) — the lowest common dependency — and are called by createMemoryMetadata, MetadataManager and MetadataFacade. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RDTnVvsgA6cUZ4xFVtPZRy
…cell ruling; spread-actual core mocks The conformance driver's DIVERGENCE era ends: RULED_CONTRACT_ANSWERS overrides the (spec-side, still pre-ruling) table for the five ruled rows — four refusals asserted with code AND status plus locating-message and nothing-stored probes, one plural row now readable — with a wiring tripwire that goes red when the spec seat lands the table half, prompting override deletion. Driver-local pins add the no-name-is-not-a-mismatch and both-directions-fold cases the table cannot carry yet. 17 metadata test files mocked @objectstack/core naming only createLogger; they now spread the actual module (the repo's existing precedent shape) so the next core export cannot break them again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RDTnVvsgA6cUZ4xFVtPZRy
…adata-roundtrip-rulings
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RDTnVvsgA6cUZ4xFVtPZRy
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 8 package(s): 33 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 6 release-owned page(s) also reference the affected code. These are read-only:
|
os-zhuang
commented
Aug 13, 2026
PM: CI red on |
| job | failing package |
|---|---|
Test Core (1/3) | @objectstack/plugin-hono-server |
Test Core (3/3) | @objectstack/driver-memory |
Temporal Conformance (live PG + MySQL) | (same run, same sha) |
The discriminator, applied
⛔ This is not a merge-queue red, so the batch-verdict reasoning does not apply — it is the PR's own run. The mechanism test (can this diff physically reach that code path?) answers yes: the new module lives in packages/core, which every one of these packages loads transitively.
Shard 1's log, read in full:
Error: ENOTDIR: not a directory, open '.../packages/spec/src/index.ts/shared'
❯ ../../core/src/metadata-service-contract.ts:82:1
❯ ../../core/src/fallbacks/memory-metadata.ts:3:1
Test Files 16 failed | 2 passed · Tests 29 passed, 0 failed.
⭐ That shape is the diagnosis. Zero failing assertions with sixteen failing files means they never loaded — nothing about the three-cell ruling's semantics is in question. The new guard imports pluralToSingular from the @objectstack/spec/sharedsubpath, and these consumers' test configs alias @objectstack/spec to the index file, so the subpath resolves to a path under a file.
driver-memory and Temporal Conformance I have only the failing job, not their error lines — so I am ⛔ not asserting they are the same signature, and the dev is told to confirm rather than inherit my inference.
Why this points at the source, not the consumers
Two independent consumer packages (plus a third job) breaking on one new import is a class, not a misconfigured package — and the module in question deliberately lives in packages/core, the lowest common dependency. Fixing consumers one at a time would leave the rest of the repo waiting to trip. That is my lean, ⛔ stated as a lean and not a ruling: the dev is measuring how many packages carry the file-mapped alias shape before choosing, and reporting the count.
Two things this exposes beyond the fix
⚠️ check:test-source-aliaswas GREEN in the dev's run and did not catch this — a gate passing while the exact class of defect it is named for ships is itself a finding. Being characterised and filed separately.⚠️ My review missed it the same way the dev did. Both of us swept@objectstack/runtimeas the downstream consumer and stopped there. "Ran a downstream consumer" is not "covered the consumer face" — least of all for a change landing in the lowest common dependency. That one is mine, not the dev's.
⛔ Nothing about the implementation review changes: the shared-guard shape, the enumeration with packages, the spec fence held at the right line, Part of #7378, and the 22-red reverse verification matching its pre-declared distribution all stand. The spec half is transferred as #8350.
Generated by Claude Code
…onfigs (#7378 patch round) Two halves, both measured before choosing: - packages/core/metadata-service-contract.ts: the '@objectstack/spec/api' import becomes TYPE-ONLY (erased at compile time) with the refusal code as a typed literal against the closed set — the storage-service.ts precedent. The module every consumer loads now makes one runtime subpath demand instead of two. '/shared' cannot be erased: pluralToSingular is a runtime value with ONE owner (copying the map would be the folk normalization the #7378 row-2 ruling forbids). - Five vitest configs alias the bare '@objectstack/spec' to spec/src/index.ts (a FILE, prefix-matched) with per-subpath entries and no '/shared' entry, so the guard's import resolved to spec/src/index.ts/shared - ENOTDIR at load: driver-memory (23 test files dead) and plugin-hono-server (16 dead) in CI, knowledge-ragflow / plugin-dev / knowledge-memory latently. Each gains the '/shared' entry in its config's own established pattern. check:test-source-alias was green throughout - its reachability walk stops at the package boundary, one hop short of the aliased dependency's own import surface; filed as #8351. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RDTnVvsgA6cUZ4xFVtPZRy
os-zhuang
commented
Aug 13, 2026
Patch round for the CI red ( Measured
Changed
Proven
The gate finding
Generated by Claude Code Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Part of #7378 — the implementation half of the maintainer's 2026-08-12 three-cell ruling. #7378 remains open: the spec-side half is out of this PR's reach by dispatch fence (any change under
packages/spec/src/**stops the card) and transfers to thedomain:specseat — see "The spec fork" below.The ruling (2026-08-12, 裁定人:维护者 huangyiirene — verbatim, untranslated)
Implementations changed, with their packages
One shared guard —
assertMetadataRegisterContract(rows 1/3) andcanonicalMetadataServiceType(row 2), new modulepackages/core/src/metadata-service-contract.ts, exported from@objectstack/core(the lowest common dependency) — called by:createMemoryMetadata@objectstack/core(src/fallbacks/memory-metadata.ts)registerrefusesgetTypeMapaccessor (all members)MetadataManager(both conformance subjects: registry-only and writabledatasource:loader)@objectstack/metadata(src/metadata-manager.ts)registerrefuses before any store or loader writeregister,registerInMemory,get,getDiagnosed,list,listDiagnosed,unregister,exists,listNames,subscribeMetadataFacade@objectstack/objectql(src/metadata-facade.ts)toKeyedDefinitionnow asserts, then keys — the interim{ name, content }boxing and the silent reconcile are both gonepackages/spec/src/contractsBoundary, stated:
registerInMemoryshares the row-2 fold (a store key is a store fact) but not the rows-1/3 refusals — the ruling namesregister, andregisterInMemoryis the boot-time seeding primitive; the guard's header records the boundary.Cell 2 cross-reference:
check:meta-type-normalized(两处裁定互相引用)The gate's existing direction, read before implementing as the ruling instructs. From
scripts/check-meta-type-normalized.mjs's own header: its job is to refuse "the REST layer making a DECISION on the un-normalized value" — the ruled fix for #3984 was structural: "normalize once at the top of each handler, let every gate read that", after three authorization bypasses (#3984, #5881, #6241) came from raw-:typecomparisons; plural→singular folding is owned bycanonicalMetaType(#4432) viaPLURAL_TO_SINGULAR(@objectstack/spec/shared). The gate's scan surface ispackages/rest/src; what this PR converges with is its DIRECTION:canonicalMetadataServiceTypefolds through the samePLURAL_TO_SINGULARmap, once, at each member's entry, so every store key reads the normalized value — and the guard module's header quotes the ruling back at the gate, closing the mutual reference. Result:register('objects', n, d)andget('object', n)address one store on every implementation, in both directions (a driver-local pin covers the reverse read too).Conformance pins — updated in this ruling PR, as the ruling requires
packages/objectql/src/metadata-service-roundtrip-conformance.test.ts: the// DIVERGENCEera ends.RULED_CONTRACT_ANSWERSoverrides the (spec-side, still pre-ruling) table for the five ruled rows:key-is-the-name-argument-object/-nonobject,primitive-data-roundtrips,array-data-roundtrips→ refused;plural-objects-type-is-its-own-store→ readable. The refusal path asserts, per subject: the rejection'scode(VALIDATION_ERROR) ANDstatus(400); the message locates (names the type, the argument, and on mismatch rows BOTH disagreeing spellings); and nothing was stored — absent under the argument key and under the document's own name. Driver-local pins add what the table cannot yet carry: no-namedocuments still register (absence is not a disagreement), agreeingdata.namestill registers, and the row-2 fold holds in the reverse direction. Note: arrays are refused with primitives — an array cannot carry document identity, and{ ...[a, b] }is{ 0: a, 1: b }corruption; the ruling's ban on coercion-into-storability decides the array row with the primitive one.The spec fork (⛔ NOT in this PR — hard fence; transfers to
domain:spec)packages/spec/src/contracts/metadata-service-roundtrip-conformance.ts— the five rows'expectedstill state pre-ruling reference semantics (andplural-objects-type-is-its-own-storeis now a misnomer).packages/spec/src/contracts/metadata-service-roundtrip-conformance.test.ts— the reference double must refuse rows 1/3 and fold row 2.packages/spec/src/contracts/metadata-service.ts—register/getTSDoc still states the superseded 2026-08-11 option-(a) ruling, including a "(c) PARKED for v18 — do not implement" paragraph that the 2026-08-12 ruling overrides.Until that lands, the objectql driver's overrides carry the ruled answers, and the wiring test "the table still lags the 2026-08-12 ruling" goes red the moment the table is updated — the mechanical handoff signal to delete each override.
Verification
origin/mainmerge @d5031f6a1):@objectstack/core33 files / 786 tests;@objectstack/metadata31 / 603;@objectstack/objectql196 / 3491 — all green. Conformance file alone: 78/78 across all four subjects, refusal paths exercised....@objectstack/coreclosure is the whole repo and is CI's half):@objectstack/runtime, the nearest consumer ofcreateMemoryMetadata(kernel fallback,/metadomain), 150 files / 2306 tests green after building its dependency closure.MetadataFacadestaying green (itsSchemaRegistryread alias pre-dated the ruling), 3 = the driver-local both-directions pin on the same three. Measured:Tests 22 failed | 56 passed (78), distribution exactly as predicted, then restored and re-run green.registercall passes a primitive/array, and both callers whosedatacarries anamederive the argument from it (objectql/plugin.ts,service-datasource/datasource-admin-plugin.ts) — no behavior change for any in-tree caller.@objectstack/metadatatest files mocked@objectstack/corenaming onlycreateLogger; converted to the repo's spread-actual precedent so the next core export cannot break them again.check:durability-log-level,check:engine-double-contract,check-engine-split-ratio,check:query-options-erasure,check:type-check-coverage,check:error-code-casing,check:nul-bytesall green; re-derived viascripts/pm/dispatch-gates.mjsagainst the real diff, which surfaced three families the dispatch list missed —check:init-service-contract,check:kernel-hook-pairs,check:test-source-alias— all run, all green.VALIDATION_ERROR+ status 400 (the ledger's own guidance: no synonym registration for a generic validation condition), so noERROR_CODE_LEDGERchange was needed — which also keeps this PR clear of the spec fence.Out of scope, untouched: the
content ?? itemunwrap inMetadataFacade.get/list(the #7519 seam — its fix wanted this PR's row-3 outcome first, which now exists: no box is ever produced).Generated by Claude Code