Skip to content

finding(metadata-protocol): 30 refusal messages open with a bracketed tag that restates the code the same throw declares — the shape #12975 rules out, and it reaches the wire #16245

Description

@claude

Found while removing [unanswerable_target] for #16145; recorded rather than fixed, because the remedy needs a ruling this seat may not make.

What is there

packages/metadata-protocol opens many refusal messages with a bracketed lowercase tag. Pairing every such throw site in protocol.ts and sys-metadata-repository.ts with the code that site declares:

tag declared code status
[invalid_request] INVALID_REQUEST 400
[item_locked] ITEM_LOCKED 403
[not_overridable] NOT_OVERRIDABLE 403
[no_draft] NO_DRAFT 404
[commit_not_found] COMMIT_NOT_FOUND 404
[metadata_conflict] METADATA_CONFLICT 409
[version_not_restorable] VERSION_NOT_RESTORABLE 409
[invalid_metadata] INVALID_METADATA 422
[writable_package_required] WRITABLE_PACKAGE_REQUIRED 422
… and the rest

Every one of them is the lowercase restatement of the code the very same throw declares. Measured: of the 31 tagged throw sites across those two files that declare a code, 30 restate it this way. (The 31st was [unanswerable_target], whose code was NOT_IMPLEMENTED — that mismatch is exactly why it was in scope for #16145 and these are not.)

Why this is a row and not just a style note

The ruling behind #12975 (maintainer, 2026-08-29) on the /data door shipping FORBIDDEN: in front of a localized refusal is: 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. These 30 tags are that condition exactly, by construction: each restates its own declared code.

They are not invisible. withoutDeclaredCodePrefix (packages/rest/src/error-response.ts) strips a prefix only when the message opens with the producer's declared code followed by a colon — INVALID_REQUEST: …. The bracketed lowercase spelling matches neither the casing nor the separator, so it is not stripped and reaches the caller in error.message.

Why it is a ruling and not a cleanup

⛔ Unlike #16145's single outlier, this family is documented, so removing it is a contract question rather than tidying:

  • packages/spec/src/api/protocol.zod.ts describes behaviour in these terms — e.g. the promotion field's text says the lookup "answers 404 [no_draft]", with the brackets.
  • That text is generated into content/docs/references/api/protocol.mdx, so the bracketed spelling is published documentation.
  • ADR-0010 and ADR-0112 both use this vocabulary, and sys_metadata_audit persists a code column whose documented example value is item_locked — a genuinely machine-readable axis using the same words.

So there is a real question underneath, and it is not "are these redundant" (measured: they are) but "is the bracketed opener load-bearing for any consumer, given the audit-row code column shares its vocabulary?" Three routes, none of them obviously right:

  1. Drop all 30 openers, exactly as 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 rules and as 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 did for the outlier, and correct the .describe() text that spells them.
  2. Keep them and declare the convention somewhere, accepting the duplication as deliberate operator affordance.
  3. Keep them only where the tag is NOT a restatement — which is the empty set after 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.

Scope note

⛔ This was deliberately not ridden along on #16145's PR: that card's diff is one string literal at the single site whose tag was not a restatement, and folding 30 documented sites plus their spec descriptions and generated docs into it would have made a one-line prose fix into a contract change.

Measurement provenance

Derived at origin/main cbca47d09, in a dedicated worktree, with git grep using plain directory pathspecs and no ** globs. The pairing was produced by walking each bracketed-tag line and reading the nearest following code assignment within the same throw; one apparent extra mismatch was checked by hand and is a docblock line, not a throw. Firing controls on the same command and scope returned 5, 6 and 140 files, so the counts are readings rather than a broken pattern.


Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions