Skip to content

rest/meta: the /references 501 back-loads its ADR-0110 D3 prescription, so the #5423 bound silently cuts it off for long object names #17584

Description

@os-justin

Filed by the os-dev seat delivering #16146 (branch claude/issue-16146-declared-refusal-relay), from a measurement taken while landing that card. Measured on origin/main merged at f721ef0f. Unassigned.

The finding

findReferencesToMeta (packages/metadata-protocol/src/protocol.ts) composes the unanswerable-target refusal as one sentence that back-loads its remedy:

References to a '<type>' item cannot be computed. A '<type>' is addressed by the composite key … while every metadata property that names a field holds the BARE field name — so no reference site can ever match this key and an empty answer would mean "not computable", not "nothing depends on it". Ask the owning object instead: GET /api/v1/meta/object/<owner>/references.

The bolded clause is the ADR-0110 D3 prescription — the half the whole #15685 family exists to deliver, because the admin "Used by" panel renders an empty answer as "Nothing in the metadata graph points at this item. Safe to delete." in front of an operator whose next click is a delete.

Every boundary that relays this message applies truncateClientMessage (packages/rest/src/error-response.ts, CLIENT_MESSAGE_MAX = 500), which cuts the tail. That helper's own docblock states the assumption it rests on, verbatim:

These messages front-load the main clause — the operator, the field, the path, what arrived and what the spec declares — and back-load attribution and issue numbers, which belong in the log rather than the response.

This message does the opposite: it back-loads the one clause that is not decoration.

Measurement

The sentence interpolates the object name twice (once inside targetName, once as owner) and the field name once, so its length grows about 3 characters per character of name. Driven over the real template:

object name / field name length message length at the bound
7 / 7 (account / owner) 412 intact
20 / 20 451 intact
30 / 30 481 intact
40 / 40 511 truncated — the prescription is the part that goes
50 / 50 541 truncated

⇒ the threshold sits at roughly 36 characters of object name plus 36 of field name. crm_opportunity_line_item_snapshot_v2 is 37. Nothing in packages/spec caps a metadata name anywhere near this, so the shape is reachable rather than theoretical.

Why this is filed now rather than repaired in place

#16146 is the change that makes the bound apply at this door at all. Before it, notImplementedRefusalAnswer (packages/rest/src/rest-server.ts) relayed the producer's message unbounded; #16146's ruling (director seat, decision batch #58, 2026-09-06) says a kept refusal is "bounded exactly as a 4xx message is (#5423)", and that PR routes the door through the shared bound accordingly. The bound is correct and is also the security floor under the refusal channel — so the repair belongs at the producer, not at the boundary: PD #12's contract-first direction, and the same "fix it where the text is authored" the prescription itself was written under.

⛔ The repair is deliberately not taken in #16146's PR: it rewrites a sentence two tests pin by content (packages/metadata-protocol/src/protocol.reference-target-unanswerable.test.ts, packages/rest/src/rest-server-meta-references-refusal-envelope.test.ts), which is a wording judgement rather than the mechanical, already-pinned shape an in-scope repair needs.

What a repair looks like

Front-load the prescription so truncation costs the explanation rather than the remedy — the sentence becomes "Ask the owning object instead: GET …. References to a '<type>' item cannot be computed, because …" — and move the two content pins with it. A second, narrower option is to shorten the middle clause; a third is to give this producer a message the bound cannot reach at any name length. Whichever is chosen, the invariant worth pinning is that the remedy survives the bound at the longest name the platform admits, measured rather than asserted.

Dedupe

search_issues with a sentence describing the defect returned exactly one row — #15685, closed, the card that made the message reach the wire at all and which does not measure the bound. Its own repair predates the bound applying here. The matcher reaches this area (that hit is the positive control); a fabricated token over the newest 79 open issues returned 0, and a targeted local grep of those same issues for truncateClientMessage|CLIENT_MESSAGE_MAX|truncat|front-load|prescription returned five rows, all unrelated by title (#17493, #17492, #17487, #17483, #17393). No open duplicate.

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

    Labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions