Skip to content

spec: register NAMESPACE_CONFLICT in ERROR_CODE_LEDGER — the ADR-0048 install-time namespace refusal now reaches a wire unregistered (the spec half of #14474) #14748

Description

@os-musk

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).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions