fix(metadata-protocol): the unanswerable-target refusal opens with prose, not a reader-less machine tag - #16244
Conversation
…ose, not a reader-less machine tag (#16145) `findReferencesToMeta` refused an unanswerable TARGET type with a message opening `[unanswerable_target]`. Since #15685 the `/references` door relays that prose verbatim, so the tag became the first thing an operator reads on the screen where they decide whether to delete something. Measured across both producer files: of 31 bracketed-tag throw sites that declare a `code`, 30 spell the tag as the lowercase restatement of their OWN declared code (`[item_locked]`/`ITEM_LOCKED`, `[no_draft]`/`NO_DRAFT`, …). This one is the sole exception -- its code is `NOT_IMPLEMENTED`, so the tag restated nothing the envelope carries, and it has zero readers repo-wide. Per the 2026-08-29 ruling behind #12975, `error` is HUMAN LANGUAGE and `code` is the MACHINE TOKEN. No status, code or envelope position moves. The absence is now pinned, since nothing pinned the tag and therefore nothing pinned its removal either. Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ Co-authored-by: Claude <noreply@anthropic.com>
…l prose (#16145) Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ Co-authored-by: Claude <noreply@anthropic.com>
…ssue-16145-drop-unanswerable-target-tag
📓 Docs Drift CheckThis PR changes 1 package(s): 2 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 — 9 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 070cd592a45db36894c645b1e035b68537ede38b && git checkout 070cd592a45db36894c645b1e035b68537ede38b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a3bbb8b1a8a3a36d474182008dd5791d5fbf67f8 2987c89164021dc193735316d05256c212f3c61f && git checkout -B drift-repro a3bbb8b1a8a3a36d474182008dd5791d5fbf67f8 && git merge --no-ff 2987c89164021dc193735316d05256c212f3c61f
node scripts/docs-audit/affected-docs.mjs --json a3bbb8b1a8a3a36d474182008dd5791d5fbf67f8
|
|
The family finding this PR's description mentions is filed as #16245 — 30 further sites in the same producer whose bracketed tag restates the code the same throw declares. That one needs a ruling rather than a patch, because those tags are spelled into Generated by Claude Code |
Fixes #16145
findReferencesToMeta's unanswerable-target refusal opened its human message with a machine-shaped[unanswerable_target]tag. This drops the tag and pins its absence. One string literal, plus a docblock paragraph, a pin and a changeset.What moves, and what does not
Same status, same
code, same envelope position, and the prescriptive sentence ADR-0110 D3 requires is untouched. The repair is at the PRODUCER:withoutDeclaredCodePrefixinpackages/rest/src/error-response.tsis not touched and not widened.The measurement that chose the remedy
The card asked whether the tag was ever a discriminator or only a habit. Measured on this branch, it is neither — it is the one member of a real family that breaks that family's own rule.
This producer writes bracketed tags on many refusals. Pairing every bracketed-tag throw site in
protocol.tsandsys-metadata-repository.tswith thecodeit declares:code[item_locked]ITEM_LOCKED[invalid_request]INVALID_REQUEST[no_draft]NO_DRAFT[unanswerable_target]NOT_IMPLEMENTED30 of the 31 tagged throw sites that declare a code restate that code in lowercase. This one is the sole exception. So the tag named a token the envelope carries on no axis — and the ruling behind #12975 (maintainer, 2026-08-29) is that
erroris HUMAN LANGUAGE whilecodeis the MACHINE TOKEN.Readers, re-derived repo-wide with a firing positive control on the same command and scope (
git grepwith no pathspec at all, so no pathspec can silently exclude anything):unanswerable_targethas 2 occurrences, both prose — the writer, and one example line in an already-merged changeset. Zero parsers, zero switches, zero assertions, zero docs. Controls on the same command fired at 5, 6 and 140 files.@objectstack/restchangeset landed at7d7ca6c0cand quotes the pre-removal sentence. That file is owned by the release process and can be consumed at any time, so this PR does not edit it; the changeset here states that it is the later word on that wire text.Does dropping the tag lose a discriminator?
No. The route has exactly two
501 NOT_IMPLEMENTEDrefusals — the unanswerable target type, and a kernel with nofindReferencesToMetaat all — and since #15685 both relay their own prose verbatim. What separates them is the whole sentence, which is entirely distinct in the two cases. The tag added no discrimination a reader did not already have, and it sat on an axis nothing can read.Declaring the fact on an axis something can read was the card's other named option. It is not taken, and not merely for scope:
notImplementedRefusalAnswermatches the literalNOT_IMPLEMENTED, and its own comment states that a second refusal code on this route falls back to the flat fault answer until someone extends it. A new code would silently undo the repair that #15685 landed.The pin
Nothing pinned the tag, so nothing pinned its removal either.
protocol.reference-target-unanswerable.test.tsgains a pin asserting thecodeandstatusenvelope, that the message does not open with a bracket, and — so it cannot pass by the message going empty — the prose it must open with instead.Ablation (firing positive control). Re-introducing the tag on disk (proved: injected-text count 1, removed-text count 0, blob
717b2f203vs HEADf11ea95ce) made exactly one test fail, the new pin, with the other 10 in the file staying green. That green is itself the re-derivation of the card's claim that the existing suites anchor on sentence CONTENT and not on the prefix. Restore proved byte-exact: blob back tof11ea95ce,git diff HEADempty, package blast radius 0.Verification
All at
2987c8916, which is this PR's head.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstackletting the script compute its own change set. Three initially returned their ownPREREQUISITE NOT METexit 3 (dual-build-cjs-loads,published-readme-exports,type-check-debt); the full workspace was then built (71/71 tasks) and all three re-ran green. Exit codes captured by redirect-then-read, never through a pipe.requirecondition, ships a README, and declares atypecheckscript, so it is inside all three swept sets.pnpm --filter @objectstack/metadata-protocol --filter @objectstack/rest typecheck— clean, scope echoed as 2 of 80 projects. The package's tsconfig reaches the edited test file (--listFileshit count 1), so this is a reading about the edit rather than around it.protocol.reference-target-unanswerable.test.ts+reference-sites.derivation.test.ts= 32 passed;rest-server-meta-references-refusal-envelope.test.ts+rest-server-meta-read-org-scope.test.ts+rest-data-door-code-prefix.test.ts= 70 passed.protocol.tsanywhere in the repo is:8082, and this diff's first change is at:21417, so no anchor can be moved by it. Theadr-anchorsentry for this file is invariant-text based, not line based.Clause-②: no
Human-language message text only. No accept set widens or narrows: the branch that raises this refusal is untouched, so exactly the same requests are refused as before, with the same
statusand the samecode. No published type surface moves — the diff contains no export, signature or type change, and the only non-test source edit is inside a string literal and a comment. The changeset is apatchon@objectstack/metadata-protocol, measured rather than assumed:privateis false andnpm pack --dry-runpacks 24dist/files and 0 test files.File collision
PR #16223 is open against the same file in a distant region (
assertRuntimeAuthoringRules/ the resolution-universe gather). This branch was cut fromorigin/main, touches only theREFERENCE_SITES.unanswerableTargetTypesbranch, and was not rebased onto it. If a conflict appears at merge time it is not resolved here.Out of scope, filed separately
The family measurement above is a finding in its own right: 30 sites spell a bracketed lowercase tag that restates the throw's own declared
code, and all of them reach the wire in front of an operator. Under the same #12975 ruling that governs this card, each is the redundant restatement that ruling removes. That is a separate, much larger card and is filed rather than ridden along here.🤖 Generated with Claude Code
https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
Generated by Claude Code