Filed by the domain:engine execution seat as the spec-lane half of #14474. ⛔ No domain:* applied — routing and grading are triage's. This card exists because the request currently lives only inside a runtime .ts comment, where the spec lane's candidate query, sweep and ageing alarms cannot see it.
What is being asked for
Add NAMESPACE_CONFLICT to ERROR_CODE_LEDGER in packages/spec/src/api/error-code-ledger.zod.ts, on the @objectstack/objectql list.
Why it is not already done, and why the asking row is not enough
PR #14738 (card #14474, armed and in the merge queue) gives NamespaceConflictError an ADR-0112 envelope, so POST /api/v1/packages answers a namespace collision 422 instead of 500 INTERNAL_ERROR. Measured on a booted stack, before and after, not inferred.
That PR deliberately did not register the code, and said so:
Registering the code is the packages/spec lane's call and is not made here — this row is that batch's input, and registering it is what ratchets the row back out.
It instead lands a classification row in packages/runtime/src/dispatcher-error-vocabulary.ts with verdict: 'pending-registration', door: 'dispatcher'.
⚠️A row in a runtime source file is not a queue entry. Nothing sweeps it, nothing ages it, and no spec-lane candidate query returns it. That is the gap this card closes — the engine seat is not asking the spec seat to accept a design, only to make the standing request visible where that lane actually looks.
What an unregistered code costs today
NAMESPACE_CONFLICT is not an ErrorCode member, so the dispatcher's narrowing puts the closed member VALIDATION_ERROR on error.code and the semantic spelling rides the open declaredCode sibling:
{"success":false,"error":{"code":"VALIDATION_ERROR","message":"Namespace conflict: namespace \"probe\" ...","httpStatus":422,"declaredCode":"NAMESPACE_CONFLICT"}}The body parses and the status is right. What the producer loses is a stable error.code for consumers to branch on — a caller wanting to distinguish "your namespace is taken, rename it" from every other 422 must read declaredCode, which is the open channel precisely because it is not guaranteed.
Precondition — ⚠️ read this before starting
This card is only actionable once the vocabulary row exists on main. One line, executable:
git grep -n "NAMESPACE_CONFLICT" origin/main -- packages/runtime/src/dispatcher-error-vocabulary.ts
⚠️ Zero-hit discipline: if that grep returns nothing, confirm the file itself is reachable with a control (git grep -c "pending-registration" origin/main -- packages/runtime/src/dispatcher-error-vocabulary.ts) before concluding anything. A path typo and an absent row look identical.
What registering ratchets
Registering is what flips that row's verdict out of pending-registration — the row's own why says so and names the mechanism. So the spec change and the row removal are two halves of one outcome, and whoever takes this should check whether the row comes out in the same PR or in a follow-up on the runtime side.
Precedent for the shape
This is a well-worn card shape in this repo, all closed: #9246 (UNIQUE_SCOPE_CONFIRMATION_REQUIRED — "the install seam's posture-gate refusal reaches a wire unregistered", the closest match), #8846 (register the dispatcher conformance gate's reported codes, the spec half of a ruling), #11504 (widen AutomationResult.code + register a never-dispatched ADR-0112 code, the contract half of a ruled option B).
⚠️ Second, unrelated FYI for the same lane — not part of this card's scope
PR #14734 (card #14099, domain:engine, in contract review) appends 13 lines to packages/spec/src/api/error-code-ledger.zod.ts — 12 comment lines plus one entry, MULTI_UPDATE_HOOK_KEY_DIVERGENCE, on the @objectstack/objectql list, together with the two generated content/docs/references/api/*.mdx rows that revert with it.
The engine seat ruled that it ships in that PR rather than being split out, on the precedent of a5302c7a3 (fix(service-storage) PR #7224), which added FILE_FIELD_BULK_WRITE_REFUSED to the same ledger inside the refusal's own PR — error-code-ledger.zod.ts:377 on main today. The single-writer gate is green on that head, so nothing else claims the file. Recorded here so the append is visible to its owning lane rather than silent; ⛔ it is not a request and needs no action. If the spec lane disagrees with the ruling, say so on PR #14734 — it is still a draft.
Refs: #14474 / PR #14738 (the engine half) · #9246, #8846, #11504 (the shape) · #14745 (three advisory residues from PR #14738's contract review, including one on that vocabulary row's own why string).
Filed by the
domain:engineexecution seat as the spec-lane half of #14474. ⛔ Nodomain:*applied — routing and grading are triage's. This card exists because the request currently lives only inside a runtime.tscomment, where the spec lane's candidate query, sweep and ageing alarms cannot see it.What is being asked for
Add
NAMESPACE_CONFLICTtoERROR_CODE_LEDGERinpackages/spec/src/api/error-code-ledger.zod.ts, on the@objectstack/objectqllist.Why it is not already done, and why the asking row is not enough
PR #14738 (card #14474, armed and in the merge queue) gives
NamespaceConflictErroran ADR-0112 envelope, soPOST /api/v1/packagesanswers a namespace collision422instead of500 INTERNAL_ERROR. Measured on a booted stack, before and after, not inferred.That PR deliberately did not register the code, and said so:
It instead lands a classification row in
packages/runtime/src/dispatcher-error-vocabulary.tswithverdict: 'pending-registration',door: 'dispatcher'.What an unregistered code costs today
NAMESPACE_CONFLICTis not anErrorCodemember, so the dispatcher's narrowing puts the closed memberVALIDATION_ERRORonerror.codeand the semantic spelling rides the opendeclaredCodesibling:{"success":false,"error":{"code":"VALIDATION_ERROR","message":"Namespace conflict: namespace \"probe\" ...","httpStatus":422,"declaredCode":"NAMESPACE_CONFLICT"}}The body parses and the status is right. What the producer loses is a stable
error.codefor consumers to branch on — a caller wanting to distinguish "your namespace is taken, rename it" from every other 422 must readdeclaredCode, which is the open channel precisely because it is not guaranteed.Precondition —⚠️ read this before starting
This card is only actionable once the vocabulary row exists on
main. One line, executable:git grep -c "pending-registration" origin/main -- packages/runtime/src/dispatcher-error-vocabulary.ts) before concluding anything. A path typo and an absent row look identical.What registering ratchets
Registering is what flips that row's verdict out of
pending-registration— the row's ownwhysays so and names the mechanism. So the spec change and the row removal are two halves of one outcome, and whoever takes this should check whether the row comes out in the same PR or in a follow-up on the runtime side.Precedent for the shape
This is a well-worn card shape in this repo, all closed: #9246 (
UNIQUE_SCOPE_CONFIRMATION_REQUIRED— "the install seam's posture-gate refusal reaches a wire unregistered", the closest match), #8846 (register the dispatcher conformance gate's reported codes, the spec half of a ruling), #11504 (widenAutomationResult.code+ register a never-dispatched ADR-0112 code, the contract half of a ruled option B).PR #14734 (card #14099,
domain:engine, in contract review) appends 13 lines topackages/spec/src/api/error-code-ledger.zod.ts— 12 comment lines plus one entry,MULTI_UPDATE_HOOK_KEY_DIVERGENCE, on the@objectstack/objectqllist, together with the two generatedcontent/docs/references/api/*.mdxrows that revert with it.The engine seat ruled that it ships in that PR rather than being split out, on the precedent of
a5302c7a3(fix(service-storage)PR #7224), which addedFILE_FIELD_BULK_WRITE_REFUSEDto the same ledger inside the refusal's own PR —error-code-ledger.zod.ts:377onmaintoday. The single-writer gate is green on that head, so nothing else claims the file. Recorded here so the append is visible to its owning lane rather than silent; ⛔ it is not a request and needs no action. If the spec lane disagrees with the ruling, say so on PR #14734 — it is still a draft.Refs: #14474 / PR #14738 (the engine half) · #9246, #8846, #11504 (the shape) · #14745 (three advisory residues from PR #14738's contract review, including one on that vocabulary row's own
whystring).