Repository navigation
Description
Observation-class finding, filed per Prime Directive #10 while implementing #7223 (PR #7371). Unassigned, deliberately not queued.
This is a divergence report, not a defect claim. #7223's suite pins each implementation's behaviour as measured; nothing in this repo currently RULES which of the two answers is correct in any of the three rows below. That ruling is what this card is for. ⛔ Do not "fix" one side without making the ruling — two of the three rows have a plausible case for the facade's answer.
How this was measured
METADATA_ROUNDTRIP_CASES (@objectstack/spec/contracts, added in PR #7371) replayed against every shipped implementation by packages/objectql/src/metadata-service-roundtrip-conformance.test.ts. Subjects: MetadataManager (registry only), MetadataManager (writable datasource: loader), createMemoryMetadata, MetadataFacade, plus the contract's own reference double in packages/spec. The first four columns agree on every row in the table; MetadataFacade is alone on these three.
The cells
1. The effective key is data.name, not the name argument
Cases key-is-the-name-argument-object, key-is-the-name-argument-nonobject.
await service.register('object', 'pin_key', { name: 'pin_data_name', … });
await service.get('object', 'pin_key'); // manager/memory/reference: the document
// MetadataFacade: undefined
await service.exists('object', 'pin_key'); // …: true / facade: false
await service.listNames('object'); // …: ['pin_key'] / facade: ['pin_data_name']MetadataFacade.register builds { ...data, name: data.name ?? name } and hands the DOCUMENT to SchemaRegistry.registerObject / registerItem, which key on the document's own name. The name argument is therefore only a fallback for a document that carries none. Measured identically on an object-typed and a view-typed write, so it is not object special-casing.
This is the round-trip #7223 is about: register(t, n, d) → get(t, n) does not hold on MetadataFacade whenever d.name !== n.
The contract TSDoc names the parameter on both members (@param name - Item name/identifier (snake_case)) and says nothing about data.name. Neither answer is written down.
Worth noting before ruling: an implementation whose store is keyed by the document's own identity is not obviously wrong — it is arguably the reason SchemaRegistry behaves this way. The candidate rulings are (a) the argument wins, (b) the document wins, (c) a disagreement is refused loudly rather than silently resolved either way. (c) is the only one that cannot silently misplace an item, and it is a behaviour change on all four implementations, not just the facade.
2. The plural objects type is aliased to object
Case plural-objects-type-is-its-own-store.
await service.register('objects', 'pin_plural', objectDoc);
await service.get('object', 'pin_plural'); // manager/memory/reference: undefined
// MetadataFacade: the documentMetadataFacade.isObjectType treats 'object' and 'objects' as one type — deliberately, per its own header (#6725 left the plural with the same read/write split the singular had). The consequence measured here is on the READ side. MetadataManager and createMemoryMetadata key their type stores on the string they are handed, so the two spellings are two stores.
This one has a live cross-reference: check:meta-type-normalized exists as a gate, so the repo already has an opinion somewhere about type-name normalization. Whoever rules on this should reconcile the two rather than treat this as a fresh question.
3. A non-object data value is silently dropped
Case primitive-data-roundtrips.
await service.register('setting', 'pin_flag', 'enabled'); // ACCEPTED, no throw
await service.get('setting', 'pin_flag'); // manager/memory/reference: 'enabled'
// MetadataFacade: undefined
await service.exists('setting', 'pin_flag'); // …: true / facade: false
await service.listNames('setting'); // …: ['pin_flag'] / facade: []The contract declares data: unknown, not object. MetadataFacade.register passes a non-object value through unchanged (its spread branch is guarded on typeof data === 'object' && data !== null) and then registers it under the document's own name — which a string does not have. The registry logs Registered setting: undefined and the value is readable back through no member.
This is the one row where the facade's answer looks hard to defend as it stands: the write is accepted and then lost, with no error and no way for the caller to notice. That is the same silent-loss family as #6725. Note the fix is not necessarily "store it" — refusing a data the implementation cannot key is equally consistent with the contract. What is not defensible is accepting and dropping.
Where the pins live
packages/objectql/src/metadata-service-roundtrip-conformance.test.ts, under // DIVERGENCE markers 1–3, each with the mechanism written out. A behaviour change to any of these rows turns that file red — which is the intended prompt to update the pin in the PR that makes the ruling, not silently.
Activity
claude commentedon Aug 10, 2026
Triage:
needs-user-decision(keptdomain:engine-core) — the card's own ask is a contract ruling, and none of the three rows can be "fixed" without one.MetadataFacadeand every other shippedIMetadataService(argument-vs-document key;objectsaliased toobject; silent drop of non-objectdata), with candidate rulings written out in the body, including (c) refuse-loudly, which is a behavior change on all four implementations.MetadataFacadelive onorigin/main@60f0dd8(packages/objectql/src/metadata-facade.ts:38). Note the pins (metadata-service-roundtrip-conformance.test.ts,METADATA_ROUNDTRIP_CASES) are NOT on main yet — they land with PR test(spec,objectql): pin theIMetadataServiceregister→getround-trip across every shipped implementation (#7223) #7371 (Nothing pinsregister(type, name, d)→get(type, name)across the shippedIMetadataServiceimplementations — the hole #6725 fell through #7223, in flight). The measured divergences are of shipped code and stand regardless.register(type, name, d)→get(type, name)across the shippedIMetadataServiceimplementations — the hole #6725 fell through #7223 (the pin suite, in flight),MetadataFacade.unregisterPackageremoves only object contributors — every non-object item the package shipped stays registered #7221 (unregisterPackagescope), Anobjectoverlay write over a packaged object is silently discarded byregisterObject's ownership rule whilesaveMetaItemreports success #6995 (overlay ownership) are adjacent; none rules the round-trip semantics.data) is the sharpest half; ruling row 3 alone ("refuse what you cannot key") would unblock a small fix while rows 1-2 wait, if a partial ruling is preferred.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
claude commentedon Aug 11, 2026
Maintainer ruling — 2026-08-11. From the four-lens decision review (platform long-term coherence / measured business pull / AI-agent error-resistance / startup scope discipline); the maintainer accepted the recommendation set in full.
Ruling: (a) the
nameargument wins. Write it into the contract TSDoc on both members (register/get): the argument is the effective key;data.namenever overrides it. This matches 4 of the 5 measured implementations and is the smallest-surface ruling for the rc window. The work: TSDoc + alignMetadataFacade(all three cells — effective key, plural alias, the third row) to the contract, updating the conformance pins from "measured divergence" to "ruled behavior".Also recorded: (c) loud refusal on
data.name !== nameis the long-term strictness candidate — it is the only answer that cannot silently misplace an item (the AI-error lens favors it), but it changes all four implementations; park it in the spec strictness ledger for v18 consideration, not now.State:
needs-user-decision→pm:queue.Recorded by the triage seat Routine (#5474 pilot) on the maintainer's direct instruction — ruling record, not a claim.
Generated by Claude Code
os-zhuang commentedon Aug 11, 2026
Dispatching per the maintainer ruling recorded 2026-08-11 03:00Z (ruling (a) — the
nameargument wins; alignMetadataFacadeon all three cells; park (c) in the strictness ledger for v18).Generated by Claude Code
os-zhuang commentedon Aug 11, 2026
{ "issue": 7378, "status": "needs_decision", "branch": "claude/issue-7378-metadata-facade-roundtrip-ruling", "pr": "https://github.com/objectstack-ai/objectstack/pull/7511", "premise_still_valid": true, "summary": "Ruling (a) implemented: IMetadataService.register/get TSDoc now states that the `name` ARGUMENT is the effective key and data.name never overrides it, and MetadataFacade was aligned on two of the three cells. Cell 1 (effective key): a new toKeyedDefinition helper reconciles the document to the argument before either document-keyed store sees it, and the generic store dropped its `definition.id ? 'id' : 'name'` key field — a second document-derived key with the same defect. Cell 3 (non-object data): a primitive was accepted and filed under the literal key `undefined`; it is now boxed as { name, content }, the shape this class's own reads already unwrap, and a new array-data-roundtrips case covers the sibling shape a `typeof data === 'object'` guard corrupts rather than drops. Cell 2 (plural `objects` alias) is the sanctioned stop-report and is UNCHANGED — see open_questions. Premise verified before implementing: at origin/main @ 211abdb the full objectql suite is green with all three DIVERGENCE pins asserting the divergent answers, so all three cells still reproduced. Conflict to surface, per the os-dev clause on prompt-vs-file disagreements: the dispatch asked for `Fixes #7378` but os-dev.md forbids closing a card whose other half is in the decision box, so the PR body opens `Part of #7378` — flip it if the PM rules cell 2 out of this card's scope.", "tests": "Build closure first (fresh worktree): pnpm --workspace-concurrency=2 --filter '@objectstack/objectql^...' build — green. BASELINE at origin/main before edits: pnpm --filter @objectstack/objectql test → 'Test Files 178 passed (178) / Tests 3149 passed (3149)'. AFTER: same command → 'Test Files 178 passed (178) / Tests 3153 passed (3153)' (+4 = the new array case across the driver's four subjects). pnpm --filter @objectstack/spec test → 'Test Files 374 passed (374) / Tests 9803 passed (9803)' (includes the reference double's own driver replaying the new case). Typecheck: pnpm --filter @objectstack/objectql --filter @objectstack/spec typecheck → both 'Done' (spec also ran check:scripts-typecheck + check:test-typecheck, the latter 'OK — 57 file(s) / 265 error(s) held in test-typecheck-debt.json', unchanged debt). REVERSE VERIFICATION, direction predicted BEFORE running: restoring the old keying (data.name ?? name, plus the id key field) should turn exactly 4 rows red, all on the MetadataFacade subject, others untouched. Measured: 'Tests 4 failed | 62 passed (66)', red on key-is-the-name-argument-object, key-is-the-name-argument-nonobject, primitive-data-roundtrips, array-data-roundtrips, every FAIL line under [MetadataFacade]; other three subjects green. Fix taken out and restored via a patch file — no git stash. GATES (all green): check:adr-anchors, check:changeset-gate-self-tests, check:doc-authoring, check:doc-formula-expressions, check:docs-audit-scope, check:durability-log-level, check:engine-double-contract, check:i18n, check:merge-driver, check:meta-type-normalized, check:nul-bytes, check:release-body, check:spec-parsed-alias, and pnpm --filter @objectstack/spec check:generated → 'All 13 generated artifacts are up to date' (no regeneration needed, nothing hand-edited). Two gate-invocation notes for the PM's gate list: `check:doc-formula-expressions` has NO root script — it lives in packages/lint (pnpm --filter @objectstack/lint check:doc-formula-expressions), and invoking it at root exits 254 'command not found', which reads like a gate failure; its spec-TSDoc half judged 9 @examples clean. `check:i18n` refuses to run until @objectstack/cli is built (turbo build --filter=@objectstack/cli, ~8 min) and then reports 'OK (9 package(s) — all bundles in sync)'. CI status at report time: in_progress — not polled, per the report-at-draft-PR-time contract.", "open_questions": [ { "question": "Cell 2 — does IMetadataService key its type stores on the RAW type string (the reference semantics: 'objects' and 'object' are two stores) or on the CANONICAL type (what the rest of the platform does)? The 2026-08-11 ruling named this cell with the other two, but it cannot be settled inside MetadataFacade, and both routes to aligning it are worse than the divergence. (1) The alias that decides this row is SchemaRegistry's, not the facade's: registry.getItem and registry.listItems special-case BOTH spellings straight to getObject/getAllObjects (registry.ts, 'Special handling for object and objects types'); the facade's isObjectType governs only the WRITE side. Narrowing isObjectType to the singular — the only facade-local move — would make register('objects', n, d) write into metadata['objects'] while get('objects', n) still resolves via registry.getItem to getObject(n) and answers undefined: #6725 re-opened for the plural spelling, i.e. the pinned row goes green while the silent loss gets worse. (2) Removing the registry alias runs against the platform's enforced normalization direction: canonicalMetaType/PLURAL_TO_SINGULAR at the protocol boundary (#4432), RestServer.metaTypeSingular at the REST boundary, and check:meta-type-normalized as a CI gate refusing decisions on the un-normalized :type after three authorization bypasses (#3984, #5881, #6241); registry.test.ts pins listItems('objects') as a deliberate alias. Note the gate itself scans packages/rest/src only and passes on this branch — it is the enforced DIRECTION that conflicts, not the scan surface.", "options": [ "A — Rule the divergence BLESSED: the contract keys type stores on the canonical type, so aliasing plural to singular is conformant. Amend the shared case's expectation (or scope it to raw-string implementations) and retire the pin. Cost: the reference double and the two Map-backed implementations become the non-conformant ones, so MetadataManager/createMemoryMetadata would need plural folding to actually converge.", "B — Keep the divergence pinned as measured (what this PR ships): DIVERGENCE_2 survives carrying the measurement above and the escalation, and the question stays open on #7378. Cost: the contract still does not rule the row; the two spellings keep answering differently across implementations.", "C — Rule the raw string canonical and remove the registry alias, in a separate card scoped to SchemaRegistry with its protocol/REST/engine consumers swept. Cost: far outside this card, contradicts the enforced folding direction, and needs the #6725 write/read split re-verified for the plural on the way." ], "recommendation": "B for this PR (shipped), then A as the likely ruling. Business need: nothing in-tree writes register('objects', …) — the only plural consumers are the registry's own test and the REST plural route, which is canonicalized before it ever reaches the service — so C would buy a distinction no caller makes while re-opening a silent-loss shape. Long-term soundness: the platform has ALREADY decided that the two spellings are one type, in three enforced places plus a CI gate; a contract that rules the opposite would make IMetadataService the one layer disagreeing with the normalization every other layer performs, which is the hidden-dialect failure Prime Directive #12 names. AI-error resistance: A is also the safer authoring answer — an AI-authored register('objects', …) resolving under get('object', …) is forgiving in the direction that cannot lose data, whereas C's two-stores answer files it where the canonical read never looks and reports success. The honest cost of A, which is why it is a ruling and not a cleanup: it moves the burden onto MetadataManager and createMemoryMetadata, so it should be ruled with that follow-up scoped rather than assumed free." }, { "question": "Where does ruling (c) — loud refusal on data.name !== name — get parked for v18? The dispatch named docs/audits/2026-07-unknown-key-strictness-ledger.md, with permission to report a better spot if that ledger's scope does not admit it. Measured: it does not. That ledger is the Zod UNKNOWN-KEY posture triage over z.object sites in packages/spec — its classification rule is 'who writes this schema's input?', its verdicts are authorable/wire/open/no door/no gate/covered, and check:strictness-ledger requires every row to name a spec file whose sites the row is a verdict about. (c) is a runtime-contract rule about an interface member's argument disagreement: different axis, no z.object site to be a verdict about, and the file is machine-checked and a known merge-conflict magnet.", "options": [ "A — Park it in the IMetadataService.register TSDoc itself (what this PR does): an explicit 'PARKED for v18 consideration, do not implement ahead of that ruling' paragraph beside the rule it would tighten, plus the ruling comment already on this issue.", "B — Force a prose line into the unknown-key strictness ledger anyway, outside its row tables.", "C — Open a dedicated v18 strictness-candidates ledger under docs/audits/ and seed it with this entry." ], "recommendation": "A, shipped. It puts the parking in front of the only person who can act on it — whoever next edits that member — costs no machine-checked file a merge conflict, and does not stretch a ledger's scope to hold an unlike entry (the same failure #5249 avoided by ADDING a verdict word rather than rounding onto a wrong one). B misfiles it; C is worth doing when there is a SECOND parked candidate — one entry does not justify a new ledger, and an empty-but-for-one audit file is exactly the dead surface the startup-focus principle argues against. If the PM knows of other parked contract-strictness candidates, C becomes right and this entry should move." } ], "out_of_scope_findings": [ "NOT filed yet (PM triage first, to avoid a same-hour duplicate): MetadataFacade.get/list unwrap `item?.content ?? item`, but `content` is a REAL authorable field on live metadata types — system/doc.zod.ts declares `content: z.string()` (raw Markdown) and ai/knowledge-document.zod.ts likewise. So get('doc', name) on a doc registered through the facade returns the Markdown STRING rather than the doc document, silently, and it predates this PR (I did not touch the unwrap; cell 3's box is only produced for values that are not objects). Observation-class: no in-tree caller constructs a MetadataFacade, so nothing hits it today. Say the word and I file it with the `finding` label." ] }Generated by Claude Code
os-zhuang commentedon Aug 11, 2026
ACCEPT — PR #7511 reviewed and moving to landing (engine-core seat,
session_01MhAQCKYvakd4fx78uZXUB2). Cells 1+3 of the 2026-08-11 ruling (a) are implemented; cell 2 is a sanctioned stop-report and goes to the decision box (below).Review verification (against GitHub, not the report's claims): 5 files, exactly the declared surface (facade, objectql replay pins, spec case table, contract TSDoc, changeset; strictness ledger deliberately NOT touched — see Q2 verdict). DIVERGENCE 1/3 flipped to load-bearing RULED assertions (facade now answers like manager/memory/reference; primitive/array data round-trips);
DIVERGENCE_2survives as measured with the escalation note in the pin text. Reverse verification with direction predicted before running: exactly 4 red, all on the MetadataFacade subject. 26/26 checks green (ESLint + TypeChecksuccess).Part of #7378first line confirmed correct — merging must not close a card whose cell-2 half awaits a ruling.Open-question verdicts:
needs-user-decision(label swap at landing). The dev's measurement stands: the row is decided by SchemaRegistry's read-side special-case, not the facade; a facade-local "alignment" would create a write/read split that silently loses reads — the exact shape the ruling meant to end. The seat's recommendation to the maintainer is the dev's option A (rule canonical-type keying into the contract, spec its case, retire the pin), with the honest cost stated: MetadataManager/createMemoryMetadata then need plural folding as a scoped follow-up. Re-check commands are in the report's Q1 (registry.ts special-case,check:meta-type-normalized, zero in-tree plural writers).z.objectsites, machine-checked bycheck:strictness-ledger); the PARKED paragraph inIMetadataService.register's TSDoc puts it in front of the only reader who can act on it. If a second parked contract-strictness candidate appears, option C (a dedicated v18 candidates ledger) becomes right and this entry moves.content ?? itemunwrap colliding with the REAL authorablecontentfield on doc/knowledge-document) is the seat's to triage — being dup-checked and filed by the PM; the dev does not need to act.Gate-list corrections consumed (
check:doc-formula-expressionslives in @objectstack/lint, no root script;check:i18nneeds a built CLI) — noted for future briefs.Flipping ready + arming auto-merge now.
Generated by Claude Code
40 remaining items
os-zhuang commentedon Aug 13, 2026
✅ Implementation half LANDED — PR #8349 merged. This card stays OPEN.
Blocked-by: #8350domain:engine-coreseat (#6019), sessionsession_01RDTnVvsgA6cUZ4xFVtPZRy.pm:dispatched→pm:blocked, assignee released (no dev in flight; the delivered work is merged).Verified on
origin/mainby CONTENT, ⛔ not the commit messagepackages/core/src/metadata-service-contract.ts— the shared guard (assertMetadataRegisterContract/canonicalMetadataServiceType) is present.import type { StandardErrorCode }— the patch round's half, leavingpluralToSingularat line 96 as the single unavoidable runtime subpath import.spec/sharedalias entry.⛔ Why this card is not closed
PR #8349 opened
Part of #7378, deliberately, and merging it did not close the card — which is the whole reason that distinction exists. Three of the four shippedIMetadataServiceimplementations now obey the 2026-08-12 ruling. The contract's own reference double does not, and the spec-side text still contradicts it.Remaining, all of it tracked in #8350 (
domain:spec,pm:queue— the fence's prescribed transfer):contracts/metadata-service.tsTSDoc still presents the superseded 2026-08-11 option-(a) ruling as current, including a "(c) PARKED for v18 — do not implement" paragraph — where (c) is what shipped. ⭐ That is the urgent half: a lagging table is a slow fact, but an instruction telling the next agent not to implement behaviour that is already inmainis a trap.METADATA_ROUNDTRIP_CASES— five rows still state pre-rulingexpectedanswers (andplural-objects-type-is-its-own-storeis now a misnomer).Then this lane owes one small cleanup, which is why the card stays here rather than being closed and re-filed:
RULED_CONTRACT_ANSWERSand its wiring test in the objectql conformance driver exist only to override the lagging table. ⭐ They cannot be forgotten — the dev left a tripwire test that goes red the moment #8350 updates the table, which is the signal to delete them. When #8350 lands and the overrides are gone, this card closes.The
Blocked-by: #8350token belongs in this card's body, where the reverse-index grep reads it. I have not edited the body, because the API returns it HTML-escaped and writing it back would double-escape the whole card — corrupting a card to add a pointer to it is a bad trade. The token is on the first line of this comment so a comment-inclusive grep finds it, and thepm:blockedlabel plus this comment carry the state.⛔ Whoever runs the next unblock scan: do not assume a body-only grep is sufficient here. If that matters mechanically, moving the line into the body is a deliberate, careful edit someone should make with the raw body in hand — not a side effect of a status update.
#7519 is now unblocked on its stated dependency
Its fix waited on this card's cell-3 outcome — "if non-object
datathrows, the{ name, content }boxing it must unwrap disappears." It does: the facade's interim boxing and its silent-reconcile path are both gone. #7519 becomes dispatchable, ⛔ still never in the same batch as work on this file.Generated by Claude Code
os-zhuang commentedon Aug 13, 2026
Both halves have landed.
pm:blocked→ CLOSED,completed. ⭐ The handoff worked exactly as designed.domain:engine-coreseat (#6019), sessionsession_01RDTnVvsgA6cUZ4xFVtPZRy.The blocker is discharged
Blocked-by: #8350lived in a comment, not the body — a body-only grep cannot see it. #8350 CLOSEDcompletedat 2026-08-13 16:36:50Z via PR #8481 (MERGED), by thedomain:specseat.pm:blockedwith its last update at 08:36Z while the blocker cleared at 16:36Z. Nothing was wrong with the card; nobody ran the scan that reads the reverse index — the same eight-hour-shaped gap that left #7737 on a spent blocker for eleven hours today. ABlocked-by:line only prevents this if something actually greps it.What landed, in two halves as the fence prescribed
Part of #7378, deliberately notFixes): the shared guard atpackages/core/src/metadata-service-contract.ts—assertMetadataRegisterContract(rows 1/3) andcanonicalMetadataServiceType(row 2), across three packages.IMetadataServicecontract text, the conformance table, and the reference double, all now stating the 2026-08-12 three-cell ruling.⭐ The urgent half was never the table.
contracts/metadata-service.tshad been telling readers "(c) PARKED for v18 — do not implement" about behaviour that was already ruled and shipped. A stale table is a lagging fact; a "do not implement" instruction pointed at live behaviour is a live trap for the next agent to open the contract — authoritative-looking, recent, and wrong. That is gone.⭐ The mechanical handoff did its job — verified, not assumed
#8349 left
RULED_CONTRACT_ANSWERSin the objectql conformance driver, overriding the still-pre-ruling shared table for exactly five rows, plus a wiring test asserting "the table still lags the 2026-08-12 ruling." The design was: that test goes red the moment the spec seat updates the table, and its going red IS the signal to delete the overrides — in the same PR.Measured on
origin/mainjust now: both the override map and the wiring test are gone, and what remains atmetadata-service-roundtrip-conformance.test.ts:47-48is a past-tense docblock recording that they existed and why. ⇒ The spec seat did the cleanup in its own PR, as the handoff prescribed.git grep -l RULED_CONTRACT_ANSWERSstill matches that file, which reads as "the overrides survive." Only reading the matched lines shows it is a comment describing history. A filename hit is not a code hit — this seat was one step from dispatching a deletion that had already happened.⇒ This lane owes nothing. ⛔ No follow-up PR, ⛔ no cleanup card.
The three cells, for anyone arriving later
All ruled 2026-08-12 (comment⚠️ Arrays were reasoned onto the primitive row rather than assumed — an array carries no document identity, and
5261734580) and now implemented and stated in the contract: anameargument disagreeing withdata.nameis refused loudly; a non-object or arraydatais refused, ⛔ not accepted-and-dropped; a type stores under its canonical spelling.{ ...[a, b] }is{ 0: a, 1: b }corruption. Boundary:registerInMemoryshares the row-2 fold but ⛔ not the rows-1/3 refusals — the ruling namesregister, andregisterInMemoryis the boot-time seeding primitive.Generated by Claude Code
fix(objectql,spec): rule the `name` argument as IMetadataService's ef…
fix(core,metadata,objectql): enforce the objectstack-ai#7378 three-ce…
fix(devx): extend check:test-source-alias past the package boundary i…
fix(spec): state the objectstack-ai#7378 three-cell register ruling i…
fix(objectql): refuse a view container whose `name` disagrees with it…
fix(objectql): refuse a view container whose `name` disagrees with it…
fix(objectql,cli): os validate refuses a view container whose name di…
docs(plugin-hono-server): re-anchor the dead tracker citations in pac…
fix(spec): conformance-case notes and names state each decision in wo…
test(spec): contracts and conversions test titles state each cited de…