fix(objectql): carry an ADR-0112 envelope on the install-time namespace conflict refusal (#14474) - #14738
Conversation
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
… string prose check:doc-authoring reds on an internal issue id inside sibling-package string prose: a runtime string reaches operators who cannot resolve one. The anchor moves to the adjacent // comment, where the reader who CAN resolve it looks. Same edit in the changeset, which compiles into release notes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
📓 Docs Drift CheckThis PR changes 2 package(s): 6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 33 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 4ac821b8c9fb7ceaed09dfc2830590860c95c4dc && git checkout 4ac821b8c9fb7ceaed09dfc2830590860c95c4dc
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin dbf115284295b1989d4648dbfbd7e5f3f96357dc 966dd3897eef76d5f3cf911bc94b4fa0dd50470d && git checkout -B drift-repro dbf115284295b1989d4648dbfbd7e5f3f96357dc && git merge --no-ff 966dd3897eef76d5f3cf911bc94b4fa0dd50470d
node scripts/docs-audit/affected-docs.mjs --json dbf115284295b1989d4648dbfbd7e5f3f96357dc
|
Contract review — PASS, adopted verbatimClause ② is yes, and the review derived that from the final diff rather than from the PR's own claim: a new (unregistered) code and a new status on a published door's wire — Performed by an isolated reviewer at the configured tier, in its own worktree, with no access to this seat's reasoning. The verdict is adopted as written; this seat did not review its own dispatch. What the reviewer checked rather than accepted
Three advisory findings, none of which downgrades the PASS
⇒ These are being filed as one
Generated by Claude Code |
Landing provenance —
|
Fixes #14474
NamespaceConflictError— the ADR-0048 install-time namespace gate's refusal, raised bySchemaRegistry.installPackagewhen a package'smanifest.namespaceis already owned by an installed package that is not a co-owner of it (ADR-0130 D1) — carriednamespace/existingPackageId/incomingPackageIdbut nocodeand nostatus. It now carriescode: 'NAMESPACE_CONFLICT'andstatus: 422: the same three-field envelope shape as its siblingArtifactObjectNameConflictError, 107 lines below it in the same file, same install-time refusal family. The message text is byte-for-byte unchanged — the prose was already correct and specific, so this adds fields rather than rewriting a sentence.The measurement came first, and the premise held
The card said its 500 was a reading — a two-hop inference across
packages/runtime/src/domains/packages.tsandpackages/types/src/thrown-http-error.ts— not an observation, and triage made measuring it binding: a card whose premise is a reading should not be closed on the same reading. So the door was observed before the class was touched, on a booted stack (@objectstack/dogfood, real HTTP): installcom.probe.alphaclaiming namespaceprobe, thenPOST /api/v1/packagesagain withcom.probe.betaclaiming the same namespace.Seed install, both runs:
SEED-STATUS 201.Before — measured against the unmodified class:
and the door logged it as server breakage:
After — same request, same probe, envelope in place:
So the premise held: the refusal really was answering as
500 INTERNAL_ERROR, telling an operator the server had broken when the truth is that their package's namespace is already taken — which invites a retry instead of a rename. The[5xx]log line is gone with it, so this also stops a client-side conflict from counting against the door's server-error signal.The vocabulary row carries the measured verdict, not the expected one
door: 'dispatcher',verdict: 'pending-registration'. Reachability is what separates this row from the three ADR-0130 install-time rows whosedoor: 'none'turns on needing an artifact install scope that no HTTP caller builds: this gate needs no scope, so an ordinary one-package install reaches it — and the measurement above is the proof rather than the call graph.pending-registrationis exactly what the after-body shows:NAMESPACE_CONFLICTis not anErrorCodemember, so the door's narrowing puts the closed memberVALIDATION_ERRORonerror.codeand the semantic spelling rides the opendeclaredCodesibling. The body parses; what the producer loses is its semantic code onerror.codeuntil a ledger row lands. Registering the code is thepackages/speclane's call and is not made here — this row is that batch's input, and registering it is what ratchets the row back out.The tests assert the envelope, and I proved they can fail
The existing tests are instance-typed (
toBeInstanceOf(NamespaceConflictError)), which is why they never caught this: they were true of a class carrying nocodeand nostatusat all. Both files now assert the envelope itself.Reverse-verified by ablation on the committed implementation — envelope fields removed, removal confirmed on disk (the
codeline 1 to 0,readonly status = 422occurrences 3 to 2, so the sibling's own field was left intact), tests re-run, then restored withgit checkout HEAD --and the restore proven byte-identical (git diff HEADempty, working blob hash equal to the HEAD blob6f4c2b2f0832efd9bc1934d7b15901fef67f1955):Exactly the two new assertions went red, one in each file, while the other 15 stayed green — the instance-typed blindness demonstrated rather than asserted.
Changeset level
@objectstack/objectql: patch+@objectstack/runtime: patch, matching the door-family precedent set by the nearest sibling #14367 (OBJECT_OWNERSHIP_CONFLICT— the same envelope-plus-vocabulary-row shape), which shipped exactly that pair. The neighbouring #14309 fix to this same door shipped patch as well.Contract review
Clause ② is yes: this puts a new status and a new registered code on a published door's wire.
needs:contract-reviewis hung on this PR as well as the card. The review is not performed in this seat.Verification
Union re-derived on the final tree with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands(its provenance line names this repo at966dd3897) and run at that commit — 34 of 36 exit 0.pnpm check:dispatcher-error-vocabulary—OK — 60 unregistered code-stamping site(s), all classified; 2 awaiting a ledger entrypnpm check:error-code-casing—no unlisted lowercase error codes in 5278 scanned file(s)pnpm check:doc-authoring,check:nul-bytes,check:type-check-debt,check:dual-build-cjs-loads, and the rest of the derived family — greenpnpm --filter @objectstack/objectql --filter @objectstack/runtime run typecheck— green, and objectql'scheck:test-typecheckcompiles the test layer, so the edited test files are actually covered rather than excludedpnpm --filter @objectstack/objectql exec vitest run src/registry-namespace-install-gate.test.ts src/registry-artifact-co-ownership.test.ts—Test Files 2 passed (2) · Tests 17 passed (17)Two gates report exit 3, which is each script's own NOT MEASURED branch and not a finding:
check-test-completenessneeds a piped test-run log CI tees and a local family run cannot hand it, andscripts/pm/check-half-states.mjsneeds a trigger-file index this container does not have. Both print that verdict themselves.One correction found by the gates and fixed in the second commit: the vocabulary row's
whystring quoted an internal tracker id, whichcheck:doc-authoringreds because a runtime string reaches operators who cannot resolve one. The anchor moved to the adjacent//comment, and the same id came out of the changeset prose, which compiles into release notes.Recovery note
This branch was recovered from a container restart that killed the seat mid-flight with the work uncommitted. The first commit is therefore a verbatim
wip:snapshot of what was on disk, pushed before anything was reviewed; the history was not reshaped afterwards because that would need a force-push. The predecessor's door measurements survived as run logs and are quoted above rather than re-asserted, and every gate, test and the ablation above were re-run in this seat on the final tree.🤖 Generated with Claude Code
Generated by Claude Code
Generated by Claude Code