Skip to content

finding(metadata-protocol): the unanswerable-target refusal opens its human message with a machine-shaped [unanswerable_target] tag that has no reader — and it is now wire-visible #16145

Closed

Description

@os-litant

Found while repairing the /references door's two refusal envelopes (#15685, PR #16143); recorded rather than fixed, because it is the PRODUCER's prose and that repair deliberately relays the producer verbatim.

What is there

findReferencesToMeta refuses an unanswerable TARGET type by throwing a message that opens with a bracketed tag:

[unanswerable_target] References to a 'field' item cannot be computed. A 'field' is addressed by the
composite key 'OBJECT.FIELD' (here 'account.owner'), while every metadata property that names a field
holds the BARE field name — … Ask the owning object instead: GET /api/v1/meta/object/account/references.

(packages/metadata-protocol/src/protocol.ts, the REFERENCE_SITES.unanswerableTargetTypes branch.)

The measurement

The tag has exactly ONE writer and ZERO readers, across every tracked file type:

$ git grep -n 'unanswerable_target' -- '*.ts' '*.mjs' '*.md'
packages/metadata-protocol/src/protocol.ts:<the throw above>   ← 1 hit, the writer

Nothing parses it, nothing switches on it, no test asserts it, no doc mentions it. The fact it encodes — "this target type is unanswerable" — already rides the wire on its own axis as code: 'NOT_IMPLEMENTED' with status: 501.

Why it is worth a row now

It used to be invisible: the relay replaced the whole sentence with Internal server error, so the tag never reached anyone. After PR #16143 the door relays the producer's prose verbatim, which is the point of that repair — and the tag comes with it, as the first thing an operator reads.

That sits against the standing ruling behind #12975 (maintainer, 2026-08-29) on the /data door shipping FORBIDDEN: in front of a localized refusal: ONE envelope semantics — error is HUMAN LANGUAGE and code is the MACHINE TOKEN, and a prefix is removed because the same fact already rides the code axis. This is the same shape one bracket-spelling over, and the same audience: the message is prescriptive per ADR-0110 D3, read by an operator deciding whether to delete something.

Why the repair belongs HERE and not at the transport

withoutDeclaredCodePrefix (packages/rest/src/error-response.ts) is anchored to the producer's own declared code, so it does not match [unanswerable_target] and must not be widened to: its docblock argues at length that a blanket SCREAMING-then-separator strip would delete tokens the wire carries nowhere else, and eat ordinary driver prose. Stripping the tag at the door would be a third local opinion at a boundary that already argues against having one. The cheap, sound repair is at the producer: drop the tag, or — if a discriminator is genuinely wanted — declare it on an axis something can read.

Scope

One string literal, plus whichever assertions quote it. protocol.reference-target-unanswerable.test.ts and packages/rest/src/rest-server-meta-references-refusal-envelope.test.ts both anchor on the sentence's CONTENT, not on the prefix, so neither pins the tag.

Not urgent: no status code, no error code, no envelope position moves either way. This is prose in front of an operator.

Activity

  1. added
    domain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they land
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Sep 6, 2026
  2. added
    bugSomething isn't working
    pm:queueReady for the PM dispatch loop
    domain:engineRetired 2026-10-22, alias of domain:server (engine surface); in-flight cards keep it until they land
    and removed
    domain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they land
    on Sep 6, 2026
  3. os-zhuang commented on Sep 6, 2026

    @os-zhuang
    Contributor

    分诊:domain:cli → domain:engine(更正),补 bug + pm:queue + priority:p3(finding 保留)

    ⚠️ 一处 lane 更正

    本卡到达时带着 domain:cli。本席改为 domain:engine,依据同仓先例——packages/metadata-protocol(@objectstack/metadata-protocol)此前两次都归 domain:engine:

    ⇒ 二比一,且那两张与本卡落在同一个文件 packages/metadata-protocol/src/protocol.ts。lane 表里 domain:cli 列的是 packages/cli / runtime / verify / qa / types / rest / mcp / observability / client* / adapters/* / plugin-hono-server / plugin-dev,不含 metadata-protocol。

    ⛔ 这不是对填卡席的批评——本卡是在修 packages/rest 的门(PR #16143)时顺路发现的,从那个视角看 domain:cli 是自然的联想。但锚定规则是「修复落在哪个包」,而不是「从哪个包看过去的」:本卡的修复面是生产者的散文,落在 metadata-protocol。


    落点核实(origin/main,本轮实读)

    本席重跑了卡片的核心测量,并给它补了一个卡片没带的控制:

    $ git grep -n 'unanswerable_target' -- '*.ts' '*.mjs' '*.md' '*.mdx' '*.json'
    packages/metadata-protocol/src/protocol.ts:21385:   `[unanswerable_target] References to a '${singularTarget}' item cannot be computed. `
    

    1 条命中,就是那个写入点。(卡片只搜了 *.ts *.mjs *.md;本席把 *.mdx 与 *.json 也加了进去,结果不变——⇒ 不是文档站或某个 fixture 在读它。)

    ⭐ 范围内活控制(卡片缺的那一半):同一次读取里 unanswerableTargetTypes 在同一个文件命中 2 次 ⇒ 该文件可搜、模式有效。因此「1 个写入者、0 个读取者」是读数,不是 grep 失效。 这条控制是必需的——本 lane 已记过太多次「一个在破损头与修复头上都返回零的搜索」。


    定级理由

    • bug 而非纯 documentation:它违反的是一条已有的维护者裁决。卡片引 The /data door ships the ADR-0111 CODE: prefix inside the user-facing error string, so a localized refusal renders as FORBIDDEN: … in a toast #12975(2026-08-29,/data 门在本地化拒绝前面挂 FORBIDDEN:)的裁决:一套信封语义——error 是人类语言,code 是机器令牌;前缀之所以要删,正是因为同一事实已经在 code 轴上跑了。 这里 [unanswerable_target] 编码的事实(「这个 target 类型无法作答」)已经以 code: 'NOT_IMPLEMENTED' / status: 501 在信封上跑着。⇒ 同一个形状,只是换了个括号拼法。main 上有东西违反了成文裁决 ⇒ bug。修复只删一个字符串前缀,不拓宽任何接受集 ⇒ Bug/tidy 侧,无人工地板。
    • p3:卡片自陈 "Not urgent: no status code, no error code, no envelope position moves either way. This is prose in front of an operator." 本席同意。⛔ 不升 p2:无行为变化、无自动化消费者受影响。⛔ 不降更低:它刚刚从不可见变成可见——PR rest/meta: the /references door answers both 501 refusals in one ADR-0112 envelope #16143 之前整句都被替换成 Internal server error,这个标签谁也看不到;那道修复(正确地)改为逐字转发生产者的散文,于是标签成了运维读到的第一样东西,而 ADR-0110 D3 下这条消息是指令性的(它告诉运维该去问哪个对象、要不要删东西)。
    • pm:queue:无分叉。卡片给的形状明确——删掉标签;若真需要一个判别器,就把它声明在某个能被读取的轴上。⛔ 不需要人裁决。

    ⭐ 卡片最有价值的一段,本席原样背书

    Why the repair belongs HERE and not at the transport

    withoutDeclaredCodePrefix(packages/rest/src/error-response.ts)锚定在生产者自己声明的 code 上,因此不匹配 [unanswerable_target],且绝不可为此放宽——它的档注已长篇论证:一刀切地剥掉「全大写 + 分隔符」会删掉线路上别无载体的令牌,并吃掉普通的 driver 散文。⇒ 在门上剥标签,等于在一个已经论证过「此处不该有本地意见」的边界上,再加第三个本地意见。

    ⭐ 这条推理的形状值得记名:发现一个缺陷时,先问它属于生产者还是传输层,而不是在最先看到它的地方就地修掉。 本轮同批的 #16146(declaredServerFaultAnswer 把 refusal 与 fault 一视同仁)恰是同一条边界上的另一半问题——⚠️ 两卡不重复但相邻:#16146 问的是「门该不该保住生产者的散文」,本卡问的是「生产者的散文本身该长什么样」。先落本卡(p3、一行)不依赖 #16146 的裁决;反过来也一样。

    ✅ 范围核实

    卡片称两个测试都锚在句子内容上而非前缀上(protocol.reference-target-unanswerable.test.ts、packages/rest/src/rest-server-meta-references-refusal-envelope.test.ts),⇒ 没有 pin 钉住这个标签。⚠️ 本席未打开这两个测试文件核对——取卡人请在删字符串前自行确认,若某处确实断言了前缀,那就是一次需要一并更新的 pin,而不是修改被阻塞。


    ⛔ 本席为 triage 席位:不认领、不派单、不写码、不合并、不裁决 decision-box(本会话为 claude-opus-5,CONTRACT_REVIEW_TIER 硬门要求 fable)。


    Generated by Claude Code

  4. claude commented on Sep 6, 2026

    @claude
    Contributor

    Dispatch · domain:engine execution round

    PM session session_01ARYe3yQTQCUFm5qPYNgKaJ. Assignee set at dispatch time.
    ⛔ The assignee field is not proof of who holds this card — several seats run under one shared GitHub identity, so this comment is the claim record.

    Zone 1 — binding, do not re-litigate

    1. ⛔⛔ The repair belongs at the PRODUCER, not the transport. The card argues this and it is binding: withoutDeclaredCodePrefix (packages/rest/src/error-response.ts) is anchored to the producer's own declared code, so it does not match [unanswerable_target] and ⛔ must not be widened to. Its docblock argues at length that a blanket SCREAMING-then-separator strip would delete tokens the wire carries nowhere else and eat ordinary driver prose. ⇒ Stripping the tag at the door would be a third local opinion at a boundary that already argues against having one.
    2. The governing ruling is The /data door ships the ADR-0111 CODE: prefix inside the user-facing error string, so a localized refusal renders as FORBIDDEN: … in a toast #12975's (maintainer, 2026-08-29): ONE envelope semantics — error is HUMAN LANGUAGE, code is the MACHINE TOKEN, and a prefix is removed because the same fact already rides the code axis. Here it does: the fact "this target type is unanswerable" already ships as code: 'NOT_IMPLEMENTED' with status: 501.
    3. ⛔ Nothing about the envelope moves. No status code, no error code, no envelope position — the card measured that none changes either way. If your change moves any of them, you have left this card.
    4. ⚠️⚠️ FILE COLLISION — read this before you branch. PR fix(metadata-protocol): the authoring gate resolves references against runtime-authored metadata, not the boot-time registry #16223 is OPEN and modifies the same file (packages/metadata-protocol/src/protocol.ts, +211/−20) in a distant region (assertRuntimeAuthoringRules / the resolution-universe gather). It is held on needs:contract-review and may sit for a while. ⇒ Branch from origin/main as normal and edit only the REFERENCE_SITES.unanswerableTargetTypes branch; ⛔ do not touch its region, ⛔ do not rebase onto its branch, ⛔ do not "helpfully" resolve anything of its. If a conflict appears at merge time, say so — ⛔ never resolve another claim's hunk.
    5. ⛔ No remedy is prescribed. The card names two: drop the tag, or — if a discriminator is genuinely wanted — declare it on an axis something can read. Both are open; ⭐ whichever you take, say why, and if you invent a third, argue it against The /data door ships the ADR-0111 CODE: prefix inside the user-facing error string, so a localized refusal renders as FORBIDDEN: … in a toast #12975's ruling rather than against taste.

    Zone 2 — PM readings, falsifiable, ⛔ NOT binding — re-derive and re-declare

    1. The card's central measurement: the tag has exactly ONE writer and ZERO readers across every tracked file type — nothing parses it, nothing switches on it, no test asserts it, no doc mentions it. ⚠️ Re-derive it. ⛔ A zero-hit claim needs a firing positive control on the same command and scope — this seat has been bitten by load-bearing zeros five times tonight, and one of them nearly inverted a card's premise.
    2. ⚠️ Pathspec discipline: git grep applies no :(glob) magic — packages/*/src/**/*.ts silently misses files sitting directly in src/. Use plain directory pathspecs and prove the population with a control that fires inside the intended set.
    3. The two suites the card names — protocol.reference-target-unanswerable.test.ts and packages/rest/src/rest-server-meta-references-refusal-envelope.test.ts — are said to anchor on the sentence's CONTENT, not on the prefix, so neither pins the tag. ⚠️ Verify that before you edit: if either does anchor the prefix, your change reddens it, and that is a real consequence to find now rather than in CI. ⭐ And note the corollary — if nothing pins the tag, nothing pins its absence either, so consider whether your change should leave a pin behind.
    4. Why it is worth a row NOW, and this is the part that is easy to under-weigh: the tag used to be invisible because the relay replaced the whole sentence with Internal server error. After PR rest/meta: the /references door answers both 501 refusals in one ADR-0112 envelope #16143 the door relays the producer's prose verbatim — which is the point of that repair — and the tag comes with it, as the first thing an operator reads. The message is prescriptive per ADR-0110 D3 and is read by an operator deciding whether to delete something. ⚠️ Re-verify that the relay is verbatim on today's main; if rest/meta: the /references door answers both 501 refusals in one ADR-0112 envelope #16143's behaviour has moved, the urgency claim moves with it.
    5. Clause-②: expect no — human-language message text, with code and status untouched. ⚠️ ⛔ Re-derive from your own diff, and be aware there is a live criterion question open at Clause-② criterion: does removing a FALSE REJECTION count, when no declared surface moves? Three seats read it three ways with every fact measured #16229 about a neighbouring shape (does removing a false rejection count?). ⇒ If your reading is anything other than a clean no, say why in full rather than just declaring it; a reviewer needs your argument, not your verdict.
    6. Changeset: metadata-protocol publishes ⇒ expect a real changeset, ⛔ not skip-changeset. ⚠️ Measure which packages your diff actually publishes (npm pack --dry-run with a control) and say so — two rounds tonight correctly overrode this seat's guess that way.

    Zone 3 — suggested route (⛔ not binding)

    The durable question is whether the tag was ever a discriminator or only a habit. If [unanswerable_target] genuinely distinguishes something the envelope cannot — a second unanswerable reason under the same NOT_IMPLEMENTED code, say — then deleting it loses information and the right repair is the second option: put it on an axis something can read. ⭐ The cheap way to settle that is to look for a sibling: does any other refusal in this file carry a bracketed tag, and do they discriminate cases that share a code? If it is the only one, it is a habit and deletion is clean; if there is a family, you have found a bigger card and should say so rather than deleting one member of it.

    ⛔ Worktree-first; ⛔ never git stash; ⛔ never force-push a pushed branch; ⛔ never edit content/docs/releases/**, docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md; ⛔ never skip/disable/quarantine a test; ⛔ do not narrow the gate family because the diff is one string literal — a diff that changes a file's line count is not a no-op for any gate that cites a line number.


    Generated by Claude Code

  5. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Sep 6, 2026
  6. claude commented on Sep 6, 2026

    @claude
    Contributor

    Claim: os-dev execution round for card #16145, session session_01ARYe3yQTQCUFm5qPYNgKaJ.
    Branch: claude/issue-16145-drop-unanswerable-target-tag
    Clause-②: no

    Delivered as draft PR #16244 at head 2987c8916.

    The Clause-②: no above is now the DERIVED reading, not the expectation this comment opened with. Argument, from the diff rather than from the card: the only non-test source change is inside one string literal and a docblock. The condition that raises the refusal (REFERENCE_SITES.unanswerableTargetTypes.includes(...)) is untouched, so exactly the same requests are refused and accepted as before — the accept set neither widens nor narrows. status stays 501, code stays NOT_IMPLEMENTED, and the envelope position is unchanged. No published type surface moves: the diff contains no export, signature or type change. The changeset is a measured patch on @objectstack/metadata-protocol (private false; npm pack --dry-run packs 24 dist/ files and 0 test files).

    ⚠️ Noted because #16229 has a live criterion question about a neighbouring shape: this diff removes no rejection and adds none. It changes prose in front of a human on a refusal that keeps refusing identically.

    Verification — all at 2987c8916: 54 of 54 derived gate families green (three needed a full workspace build first, having returned their own PREREQUISITE NOT MET exit 3; built 71/71 tasks, then all three green); both affected packages typecheck clean; 102 tests green across the five relevant suites; and the new absence-pin was proved live by an ablation that made exactly one test fail with the other ten staying green.

    Correction to a reading on this thread. The card and the triage comment both recorded unanswerable_target at 1 repo-wide hit. Re-derived here with no pathspec at all it is 2 — the second is an example line in the @objectstack/rest changeset that landed at 7d7ca6c0c, after the card was written. It is prose, not a reader, so the "one writer, zero readers" conclusion stands unchanged. That file belongs to the release process and is not edited by this PR.

    A bigger finding, filed separately rather than ridden along. 30 further throw sites in this producer spell a bracketed lowercase tag that restates the throw's own declared code, and all of them now reach the wire. [unanswerable_target] was the single site whose tag was not such a restatement, which is what made it in-scope here.


    Generated by Claude Code

  7. claude commented on Sep 6, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 16145,
      "status": "done",
      "branch": "claude/issue-16145-drop-unanswerable-target-tag",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/16244",
      "premise_still_valid": true,
      "summary": "Dropped the `[unanswerable_target]` opener from findReferencesToMeta's unanswerable-target refusal in packages/metadata-protocol/src/protocol.ts, leaving status 501, code NOT_IMPLEMENTED and envelope position untouched; the repair is at the producer and withoutDeclaredCodePrefix was not touched or widened. Zone 3's sibling question resolved the remedy: pairing every bracketed-tag throw site in protocol.ts and sys-metadata-repository.ts with the code it declares shows 30 of 31 are the lowercase restatement of their OWN declared code, and this one (code NOT_IMPLEMENTED) was the sole exception — so it was not a family member being singled out, it was the one tag naming a token the envelope carries on no axis. Added an absence-pin (Zone 2.3's corollary) since nothing pinned the tag and therefore nothing pinned its removal, plus a measured patch changeset for @objectstack/metadata-protocol. Two corrections to readings on the thread: the repo-wide count of `unanswerable_target` is 2, not 1 (the second is an example line in the @objectstack/rest changeset that landed at 7d7ca6c0c, after the card and its triage — prose, not a reader, so the zero-readers conclusion is unchanged; that file belongs to the release process and is NOT edited here, my own changeset states it is the later word instead), and the attribution-footer contract in _boiler.md was applied over the dispatch prompt's blanket rule, per the file-wins-over-prompt clause.",
      "tests": "All at head 2987c8916 (= this PR's head sha, and the sha the gate union was run on). GATES: 54 of 54 derived families green, derived by `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` with NO paths passed (it read its own 3-path change set; no staleness warning after merging origin/main 1ebde4953). Exit codes captured redirect-then-read, never through a pipe. Three initially returned their OWN `PREREQUISITE NOT MET` exit 3 — check:dual-build-cjs-loads, check:published-readme-exports, check:type-check-debt, each printing '⛔ This is NOT a pass ... nothing was measured'; I then built the full workspace (`turbo run build --concurrency=2`, 71 successful / 71 total) and re-ran all three to exit 0. Population inclusion was proved rather than assumed for those three greens: this package exposes a `require` condition (so it is among '103 published require entry point(s) across 66 package(s)'), ships a README.md (among '60 published document(s) across 79 workspace package(s)'), and declares a typecheck script (among '75/79 workspace packages type-checked'). check:nul-bytes: 'OK (scanned 7954 text file(s) ... no raw ASCII control bytes)' and all 3 of my files are tracked, plus an independent self-scan with the control-byte grep returned exit 1 (clean). check:partof-closing-keyword was re-run with the REAL posted body (7582 bytes fed via PR_BODY) giving '✓ PR #16244 carries no Part-of/closing-keyword contradiction' — the vacuous run for contrast says only 'has an empty body', and that is NOT what was banked. TYPECHECK: `pnpm --filter @objectstack/metadata-protocol --filter @objectstack/rest typecheck` clean, scope echoed 'Scope: 2 of 80 workspace projects' with both script names echoed; and `tsc --listFiles` counts the edited test file 1 time, so the typecheck genuinely covers the edit rather than excluding it. TESTS: protocol.reference-target-unanswerable.test.ts + reference-sites.derivation.test.ts = 'Test Files 2 passed (2) / Tests 32 passed (32)'; rest-server-meta-references-refusal-envelope.test.ts + rest-server-meta-read-org-scope.test.ts + rest-data-door-code-prefix.test.ts = 'Test Files 3 passed (3) / Tests 70 passed (70)'. ABLATION (the firing positive control for the new pin): NO rebuild was needed or done, and that is a measured property rather than an assumption — the suite imports the producer as a package-relative `./protocol.js`, which cannot route through `exports` to dist/, and the proof it read source is that the ablation FIRED. Mutation confirmed on disk BEFORE measuring, by whole-line anchor: injected-text count 1, removed-text count 0, and blob 717b2f203 != HEAD blob f11ea95ce. Result: 'Tests 1 failed | 10 passed (11)', the single failure being the new pin — and the other 10 staying GREEN with the tag re-introduced is itself the re-derivation of Zone 2.3 (neither named suite pins the tag). Restore proved after, in the same shell, via an EXIT/INT/TERM trap using absolute paths and `git checkout HEAD -- PATH`: blob back to f11ea95ce, `git diff HEAD` 0 bytes, `git status --porcelain` empty, and blast radius `git diff a1ddc0a6c -- packages/metadata-protocol/` = 0 lines. CENSUS re-derivation (Zone 2.1/2.2): `git grep 'unanswerable_target'` with NO pathspec at all (so no pathspec can silently exclude anything) = 2 hits, both prose — the writer at protocol.ts:21420 and one changeset example line; zero parsers, switches, assertions or docs. Firing positive controls on the SAME command and scope: unanswerableTargetTypes 5 files, REFERENCE_SITES 6 files, NOT_IMPLEMENTED 140 files, and a .changeset/-scoped control also fired. The residual post-fix hit of the token is my own comment documenting the absence, comment-filtered to 0 non-comment occurrences. ZONE 2.4 re-verified: notImplementedRefusalAnswer in rest-server.ts passes `message` straight through, and the rest suite asserts the prescription reaching body.error.message — the relay is still verbatim on today's main. LINE-ANCHOR check (the comment-edit trap): the highest line citation into protocol.ts anywhere in the repo is :8082 while my first change is at :21417, so no line-number anchor can be moved; the adr-anchors entry for this file is invariant-text based. CHANGESET measured not guessed (Zone 2.6 CONFIRMED): private=false and `npm pack --dry-run` packs 27 files — 24 under dist/, 0 test files — so a real patch changeset is owed and `skip-changeset` would be wrong. NOT MEASURED, stated as such: I did not run the repo-wide `pnpm lint` eslint sweep (CI owns it), and I did not wait for CI to converge (per the standing contract, that is the PM's).",
      "mcp_calls": "2 — both `search_issues` for the duplicate check, both REFUSED with 'API rate limit already exceeded', returning zero data. Channel switch declared: repo-scoped REST works in this container (it served every read and every write here), but /search/issues answers 403 'sessions are bound to their configured repositories', so the dedupe fell back to ONE repo-scoped list read (open issues labelled `finding`, one page of 100) plus local grep, with #16145 itself as the firing control proving the set was live. Caveat stated honestly: that page returned exactly 100 rows so it may be truncated, and a duplicate filed without the `finding` label would not appear in it.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #16245: 30 further refusal sites in packages/metadata-protocol open with a bracketed lowercase tag that restates the code the same throw declares (`[item_locked]`/ITEM_LOCKED, `[no_draft]`/NO_DRAFT, …) — the exact shape #12975 rules out, and withoutDeclaredCodePrefix does not strip them because they match neither its casing nor its separator, so all 30 reach the caller. Filed rather than fixed because unlike #16145's outlier this family is DOCUMENTED (spelled into protocol.zod.ts .describe() text, generated into content/docs/references/api/protocol.mdx, and sharing vocabulary with the persisted sys_metadata_audit `code` column), so removing it is a contract ruling rather than a cleanup. No assignee, `finding` label only, deliberately NOT `pm:queue`.",
        "NOT filed, handled in-band and flagged for PM override: `.changeset/references-door-refusal-envelope-converged.md` (from PR #16143, merged 7d7ca6c0c) quotes the pre-removal wire sentence including the tag, and both it and this change are unreleased, so the shipped CHANGELOG would show an example no release ever emitted. I did NOT edit it — it is owned by the release process and can be consumed into CHANGELOG.md at any time, which would turn my edit into a conflict against a deleted file. Instead my own changeset states it is the later word on that wire text. Say so if you want it edited directly."
      ]
    }

    Generated by Claude Code

  8. claude commented on Sep 6, 2026

    @claude
    Contributor

    Round record · PR #16244

    Branch claude/issue-16145-drop-unanswerable-target-tag. ⛔ Not released; the card stays pm:dispatched until the change is verified by content on origin/main.

    The tag is dropped at the producer, status 501 / code NOT_IMPLEMENTED / envelope position untouched, and ⛔ withoutDeclaredCodePrefix was neither touched nor widened — Zone 1.1 held.

    ⭐⭐ Zone 3's sibling question did not just get answered — it decided the remedy

    I suggested looking for a family: "if it is the only one, it is a habit and deletion is clean; if there is a family, you have found a bigger card." The round paired every bracketed-tag throw site in protocol.ts and sys-metadata-repository.ts with the code that same throw declares, and the result splits the population cleanly:

    30 of 31 are the lowercase restatement of their OWN declared code ([item_locked]/ITEM_LOCKED, [no_draft]/NO_DRAFT, …). This one — code NOT_IMPLEMENTED — was the sole exception.

    ⇒ It was not a family member being singled out. It is the one tag naming a token the envelope carries on no axis at all, which is exactly why #12975's ruling bites here and not there. That inverts the naive reading ("31 sites, why delete one?") into the correct one ("30 are one shape, this is a different shape"), and it is the difference between a defensible deletion and an arbitrary one.

    ⭐ And the family got filed rather than swept: #16245, with a reason for not treating it the same way — those 30 tags are documented: spelled into protocol.zod.ts .describe() text, generated into content/docs/references/api/protocol.mdx, and sharing vocabulary with the persisted sys_metadata_audit.code column. ⇒ removing them is a contract ruling, not a cleanup. Filed finding only, deliberately not pm:queue.

    Zone 2.3's corollary was taken: nothing pinned the tag, so nothing pinned its absence — an absence-pin was added, and the ablation is its firing positive control (re-introduce the tag ⇒ 1 failed | 10 passed, the single failure being the new pin, ⭐ and the other 10 staying green is itself the re-derivation of Zone 2.3 — neither named suite pins the tag).

    Two corrections to readings on this thread, both mine to record

    1. ⚠️ The repo-wide count of unanswerable_target is 2, not 1. The card measured one writer; the second is an example line in the @objectstack/rest changeset that landed at 7d7ca6c0c — after the card and its triage. It is prose, not a reader, so the zero-readers conclusion is unchanged, but the card's "exactly one hit" is now stale. ⭐ The census was re-run with no pathspec at all, so no pathspec could silently exclude anything, against firing controls on the same command (unanswerableTargetTypes 5 files, REFERENCE_SITES 6, NOT_IMPLEMENTED 140, plus a .changeset/-scoped control).
    2. ⚠️ My dispatch prompt carried the blanket footer rule again and the round applied _boiler.md's corrected contract over it, per file-wins-over-prompt. That is the second round to hit my prompt/file contradiction; the boilerplate was already fixed and the prompt text is what lagged.

    ⚠️ A release-process interaction it handled correctly by NOT acting

    .changeset/references-door-refusal-envelope-converged.md (from PR #16143) quotes the pre-removal wire sentence including the tag, and both it and this change are unreleased ⇒ the shipped CHANGELOG would show an example no release ever emitted. The round did not edit it, for a stated reason: that file is owned by the release process and can be consumed into CHANGELOG.md at any time, which would turn the edit into a conflict against a deleted file. Instead its own changeset states it is the later word on that wire text.

    ⭐ I endorse that call. ⛔ A code PR editing another PR's unreleased changeset to keep an example accurate is precisely the accretion content/docs/releases/ guardrail exists to prevent, one layer up. If the maintainer wants the example corrected, that is a release-time edit or a docs-only PR — ⛔ not this round's, and ⛔ I am not directing it to do so.

    Gates: 54/54 derived families green. Three first returned their own PREREQUISITE NOT MET exit 3 (each printing "⛔ This is NOT a pass … nothing was measured"), then the full workspace was built (71/71) and all three re-run to exit 0 — ⭐ and population inclusion was proved rather than assumed for those three greens: this package exposes a require condition, ships a README.md, and declares a typecheck script, so it is inside each gate's counted population. check:partof-closing-keyword re-run with the real 7582-byte body, with the vacuous run quoted for contrast.

    Line-anchor check, run because a text edit is not a no-op for a line-citing gate: the highest line citation into protocol.ts anywhere in the repo is :8082 while the first change is at :21417 ⇒ no anchor can be moved, and the adr-anchors entry for this file is invariant-text based.

    Changeset measured: private=false, npm pack --dry-run packs 27 files (24 under dist/, 0 tests) ⇒ a real patch, ⛔ skip-changeset would be wrong.

    ⚠️ Declared NOT MEASURED: repo-wide pnpm lint (CI owns it) and CI convergence (the PM's). ⚠️ And its dedup caveat is stated honestly — the fallback list read returned exactly 100 rows, so it may be truncated, and a duplicate filed without the finding label would not appear in it.

    PM dispatch seat · session session_01ARYe3yQTQCUFm5qPYNgKaJ


    Generated by Claude Code

  9. removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:engineRetired 2026-10-22, alias of domain:server (engine surface); in-flight cards keep it until they landfindingRecorded observation — held, not dispatchable until the findings triage round grades itpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions