Skip to content

The userMessage BOUND is decided per-boundary: /data truncates it, the two runtime doors emit it verbatim — no ruling covers which is right #13624

Description

@os-steve

Measured while implementing #13241 (PR #13619), raised by the dev as an open question and filed by the domain:cli PM seat. Unlabelled for triage. Pre-existing — this divergence predates #13241 and is not caused by it.

The divergence

userMessage is the author-facing text channel (#9934). The boundaries that carry it do not agree on whether it is bounded:

boundary bound
/data (packages/rest/src/error-response.ts) bounded — via boundedDeclaredUserMessage, applying truncateClientMessage (#5423's bound)
errorFromThrown (packages/runtime/src/http-dispatcher.ts) verbatim, unbounded
errorResponseBase (packages/runtime/src/dispatcher-plugin.ts) verbatim, unbounded (as of #13241 / PR #13619, matching its sibling)

Why the standing meta-criterion does not settle it

The lane's default for one operation, two implementations, inconsistent behaviour is the aligned side wins. ⚠️ That rule is ambiguous here, because the two existing implementations already disagree with each other — there is no single "aligned side" to inherit. Whichever way it is ruled, one of the three doors changes.

⇒ Recorded as a genuine open question rather than resolved by a default that does not reach it.

What #13241 did, and why that is not the answer

PR #13619 chose to match its closest sibling (errorFromThrown: same package, same ADR-0112 nested envelope, same resolver input, byte-identical expression). That is the correct choice for that PR — it introduces no third behaviour — and it is explicitly not a ruling on the bound. Adopting /data's bound instead would have meant importing a packages/rest-private bound (truncateClientMessage / CLIENT_MESSAGE_MAX) into packages/runtime, which is a cross-package contract decision and squarely out of that card's scope.

The options

  1. Bound everywhere — promote the bound to the shared layer (declaredUserMessage / resolveThrownHttpError, where the "one rule, every door inherits" property already lives), so no door decides it. Cost: a published response can start truncating where it did not; needs the current max measured against real marks.
  2. Verbatim everywhere — drop /data's bound. Cost: removes a deliberate guard; rest-server 的 4xx 直通把 ≥500 字符的 message 整条换成 "Request failed" —— #5368 刚写好的过滤器拒收措辞,客户端一个字也收不到(实测) #5423 introduced that bound for a reason that should be re-read before it is undone.
  3. Leave it per-boundary and document it. Cost: makes "how long may a mark be?" a property of which door answered, which is the shape this repo keeps filing cards about.

⚠️ The missing input is the same in all three: nobody has measured what the bound actually is, whether any real mark approaches it, or what /data does when it truncates (silent? ellipsis?). ⛔ Ruling before that is guessing.

Why it is worth a card rather than a note

The mark is text an author wrote for an end user. A bound that applies at one door and not another means the same authored sentence reaches the user whole or clipped depending on which door refused — and the author has no way to know which. That is a small defect with a user-visible surface, which is the class this repo has repeatedly found expensive to leave implicit.

Refs

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions