Skip to content

fix(metadata-protocol): the unanswerable-target refusal opens with prose, not a reader-less machine tag - #16244

Merged
zhuangjianguo merged 3 commits into
mainfrom
claude/issue-16145-drop-unanswerable-target-tag
Sep 6, 2026
Merged

fix(metadata-protocol): the unanswerable-target refusal opens with prose, not a reader-less machine tag#16244
zhuangjianguo merged 3 commits into
mainfrom
claude/issue-16145-drop-unanswerable-target-tag

Conversation

@claude

@claude claude Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

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

before  501 {"error":{"code":"NOT_IMPLEMENTED","message":"[unanswerable_target] References to a 'field' item cannot be computed. … Ask the owning object instead: GET /api/v1/meta/object/account/references."}}
after   501 {"error":{"code":"NOT_IMPLEMENTED","message":"References to a 'field' item cannot be computed. … Ask the owning object instead: GET /api/v1/meta/object/account/references."}}

Same status, same code, same envelope position, and the prescriptive sentence ADR-0110 D3 requires is untouched. The repair is at the PRODUCER: withoutDeclaredCodePrefix in packages/rest/src/error-response.ts is 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.ts and sys-metadata-repository.ts with the code it declares:

tag declared code relation
[item_locked] ITEM_LOCKED lowercase restatement
[invalid_request] INVALID_REQUEST lowercase restatement
[no_draft] NO_DRAFT lowercase restatement
… 27 more sites … lowercase restatement
[unanswerable_target] NOT_IMPLEMENTED not a restatement

30 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 error is HUMAN LANGUAGE while code is the MACHINE TOKEN.

Readers, re-derived repo-wide with a firing positive control on the same command and scope (git grep with no pathspec at all, so no pathspec can silently exclude anything): unanswerable_target has 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.

⚠️ The card and its triage both recorded 1 hit. The second occurrence is real and post-dates them: the @objectstack/rest changeset landed at 7d7ca6c0c and 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_IMPLEMENTED refusals — the unanswerable target type, and a kernel with no findReferencesToMeta at 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: notImplementedRefusalAnswer matches the literal NOT_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.ts gains a pin asserting the code and status envelope, 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 717b2f203 vs HEAD f11ea95ce) 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 to f11ea95ce, git diff HEAD empty, package blast radius 0.

Verification

All at 2987c8916, which is this PR's head.

  • 54 of 54 derived gate families green. Derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack letting the script compute its own change set. Three initially returned their own PREREQUISITE NOT MET exit 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.
  • Each of those three greens was checked for population inclusion rather than taken on its exit code: this package publishes a require condition, ships a README, and declares a typecheck script, 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 (--listFiles hit count 1), so this is a reading about the edit rather than around it.
  • Tests: 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.
  • Line-number anchors: the highest line citation into protocol.ts anywhere in the repo is :8082, and this diff's first change is at :21417, so no anchor can be moved by it. The adr-anchors entry 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 status and the same code. 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 a patch on @objectstack/metadata-protocol, measured rather than assumed: private is false and npm pack --dry-run packs 24 dist/ 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 from origin/main, touches only the REFERENCE_SITES.unanswerableTargetTypes branch, 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

…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>
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tests tooling labels Sep 6, 2026
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/metadata-protocol, touching 4 documentable anchor(s).

2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/client-sdk.mdx (via getReferences (sdk, the bare tail of client method meta.getReferences, bound to GET /api/v1/meta/:type/:name/references), meta.getReferences (sdk, the route ledger binds it to GET /api/v1/meta/:type/:name/references, selected by route anchor /:type/:name/references))
  • content/docs/ui/react-pages.mdx (via /:type/:name/references (route, bridged from symbol findReferencesToMeta — its route source's handler names it))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17.mdx (via findReferencesToMeta (symbol, a method of class ObjectStackProtocolImplementation))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 9 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json a3bbb8b1a8a3a36d474182008dd5791d5fbf67f8packageMentionDocs.

Which tree this was computed on

This run read content/docs from 070cd592a45db36894c645b1e035b68537ede38b — the merge of head 2987c89164021dc193735316d05256c212f3c61f into base a3bbb8b1a8a3a36d474182008dd5791d5fbf67f8, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs a3bbb8b1a8a3a36d474182008dd5791d5fbf67f8 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@claude

claude Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

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 protocol.zod.ts descriptions and generated into the published API docs, so it is deliberately not ridden along here.


Generated by Claude Code

@zhuangjianguo
zhuangjianguo marked this pull request as ready for review September 6, 2026 08:10
@zhuangjianguo
zhuangjianguo added this pull request to the merge queue Sep 6, 2026
Merged via the queue into main with commit 7d711c9 Sep 6, 2026
35 checks passed
@zhuangjianguo
zhuangjianguo deleted the claude/issue-16145-drop-unanswerable-target-tag branch September 6, 2026 08:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s tests tooling

Projects

None yet

2 participants