Skip to content

finding(objectql): NamespaceConflictError (the ADR-0048 install-time namespace gate's refusal) carries no ADR-0112 envelope — no code, no status — and POST /packages would answer it as 500 by reading #14474

Description

@os-musk

Found while implementing #14367 (registerObject's cross-package ownership refusal gaining an ADR-0112 envelope). Out of that card's scope — its file surface is region-bounded to the registerObject refusal and one new class beside ArtifactObjectNameConflictError, and this class sits inside the region the in-flight #14309 dispatch is editing — so filed rather than fixed.

The site

NamespaceConflictError in packages/objectql/src/registry.ts (export class NamespaceConflictError extends Error, ~:1137 on origin/main @ a98b61b3e) carries namespace / existingPackageId / incomingPackageId as fields but no code and no status. It is the ADR-0048 install-time namespace gate's refusal, thrown by SchemaRegistry.installPackage when a package's namespace is already owned by an installed package that is not a co-owner of it (ADR-0130 D1).

Why it matters — reading, not runtime measurement

Unlike the #14367 refusal, installPackage IS on an HTTP path: POST /packages in packages/runtime/src/domains/packages.ts calls protocol.installPackage (or registry.installPackage as fallback), and the domain's terminal catch answers deps.errorFromThrown(e, 500). resolveThrownHttpError (packages/types/src/thrown-http-error.ts) reads .status / .statusCode off the throw and falls to the caller's fallback when neither is present — so, by reading, a namespace conflict on POST /packages answers 500 INTERNAL_ERROR with the refusal's prose as the message, where a refusal the platform DECIDES should answer 4xx with a registered code. Not measured against a running door in this filing; the reading is from the two call sites and the resolver, and a dev on this card should measure it first.

What the sibling refusals already do

DUPLICATE_ARTIFACT_PACKAGE / INVALID_ARTIFACT_PACKAGE_ENTRY (packages/objectql/src/artifact-packages.ts), DUPLICATE_ARTIFACT_OBJECT_NAME (ArtifactObjectNameConflictError, same file as this class) and, once #14367 lands, OBJECT_OWNERSHIP_CONFLICT all carry code + status: 422 and a classification row in packages/runtime/src/dispatcher-error-vocabulary.ts. This class is the one remaining install-time refusal in that family without an envelope.

Existing tests on this path

registry-namespace-install-gate.test.ts asserts toThrowError(NamespaceConflictError) / toBeInstanceOf(NamespaceConflictError) and registry-artifact-co-ownership.test.ts asserts toBeInstanceOf(NamespaceConflictError) — instance-typed, so not the bare-toThrow() false-green #14367 removes, but none can assert an envelope because there is none.

Suggested shape (not prescriptive)

Add code (SCREAMING_SNAKE, e.g. NAMESPACE_CONFLICT) + status: 422 to the class, message unchanged; measure the POST /packages door's answer before and after (this refusal, unlike #14367's, may need a pending-registration verdict and a ledger entry, since errorFromThrown would then put its status on the wire); add the vocabulary row with the measured verdict.

Blocked-by: none (the pattern exists; #14367 only shares the file).

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions