Skip to content

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

Description

@os-litant

Measured on origin/main@f4e741bd1 (re-confirmed on ae4cfcce7). Filed unassigned from the #12260 dev seat; not a regression from #12260 — see the last section.

The measurement

classifyDataError's declared-status 4xx arm ships the producer's message verbatim:

packages/rest/src/error-response.ts:1019-1033

const msg = typeof error?.message === 'string' && error.message.length > 0
    ? truncateClientMessage(error.message)
    : 'Request failed';
return { status: declaredStatus, body: { error: msg, ...thrownCodeFields(error, declaredStatus), ... } };

The sharing middleware's by-id write refusal declares { code: 'FORBIDDEN', status: 403 }, so it takes exactly this arm. Its thrown message is the ADR-0111 CODE: message idiom, and the whole thing — machine prefix included — becomes the flat body's human-readable error, which Console and the reporting deployment's own client render verbatim.

Pinned byte-for-byte by tests that assert the prefix survives:

  • packages/rest/src/rest-4xx-message-truncation.test.ts:123expect(r.body.error).toBe(msg) where msg starts FORBIDDEN:
  • packages/rest/src/rest-5xx-status-passthrough.test.ts:301expect(r.body).toEqual({ error: msg, code: 'FORBIDDEN', object: 'showcase_inquiry' })

The two doors disagree, and one of them says so in prose

The share-route family and handleApprovalError both strip it:

  • packages/rest/src/rest-server.ts:10867res.status(status).json({ code, error: msg.replace(/^[A-Z_]+:\s*/, '') })
  • respondSharingError's ADR-0111 prefix arm, same file

And rest-server.ts's own #8111 comment above that arm states:

The prefix is a SERVER-INTERNAL service→REST derivation: it is stripped below and never reaches the wire, so no consumer can read it

That is true for that door and not true for /data. The same refusal therefore reaches a client with the prefix on one route and without it on another — the door-disagreement shape #7525 / #8016 / #11588 keep producing.

Why it is worth a card now rather than before

#12260 converted the sharing write denial's sentence onto the Operation Message Catalog, so what a zh-CN user now reads on /data is:

FORBIDDEN: 您无权修改或删除这条记录,如需修改请联系该记录的负责人或管理员。

The actionable half is localized human language; the machine token glued in front of it is now the only non-human fragment left in the user's face. Before that conversion the whole sentence was English, so the prefix was not the salient part.

Not a regression, and deliberately out of #12260's scope

The prefix rode in front of the old English sentence identically — nothing about its handling moved. It is left alone there on purpose: the fence on that card was that the prefix is wire contract and must not be shadowed, and changing which bytes /data ships is a REST envelope decision, not a plugin one. Any fix moves the two pinned assertions above, which are legitimate wire-behaviour tests today.

Options, not a recommendation

  1. Strip the CODE: prefix in the declared-4xx arm, matching the share door. Changes the flat 4xx body for every producer using the idiom, and moves both pinned tests.
  2. Leave error alone and have the producers mark userMessage (the rest: respondSharingError's nested re-dress also drops userMessage and the producer's structured context that /data carries for the same throw #12669 channel withDeclaredUserMessage already puts on the flat body top level at error-response.ts:569-571). Strictly additive on the wire; needs each emitter to opt in.
  3. Rule that the prefix in error is intended on /data and correct the #8111 comment, which currently asserts the opposite.

Option 2 looks cheapest and least contract-moving, but which door is right is an envelope decision for the maintainer rather than something a dev seat should pick.

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions