Skip to content

fix(rest): a declared 5xx status survives mapDataError on the CRUD data routes - #7402

Merged
os-help merged 3 commits into
mainfrom
claude/issue-5582-mapdataerror-5xx-passthrough
Aug 10, 2026
Merged

fix(rest): a declared 5xx status survives mapDataError on the CRUD data routes#7402
os-help merged 3 commits into
mainfrom
claude/issue-5582-mapdataerror-5xx-passthrough

Conversation

@os-help

@os-help os-help commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Fixes #5582

rest-server.ts had two error doors giving opposite answers to one question — "the producer declared a status":

door range routes
resolveErrorResponse (sendError / handleRouteError) 400-599 metadata, UI, discovery, batch
mapDataError 400-499 the CRUD data routes — ~11 direct call sites that bypass the above

So on the data routes a declared 5xx lost its declaration twice over: the passthrough skipped it, and the status was then re-derived from the message text by the sanitizing heuristics — or, matching none of them, fell out of UNCLASSIFIED_FAULT as 500 INTERNAL_ERROR.

What changed

mapDataError's gate is now 400-599, the same door resolveErrorResponse opens, with the disposition #5437 / PR #5464 already ruled for a declared server fault: keep the status, keep the machine-readable code, drop the prose. The issue's own words for the fix, verbatim:

mapDataError 的直通改为与 resolveErrorResponse 同款:4xx 截断措辞、5xx 保状态丢措辞留 code

The 4xx arm is untouched — wording truncated rather than erased (#5423), object retained.

The live producer (#5907)

driver-sql / driver-turso throw status: 501 + code: NOT_IMPLEMENTED for an aggregate function @objectstack/spec declares and the backend cannot compile (count_distinct / array_agg / string_agg). Those names clear metadata-protocol's shape gate, so the throw reaches a CRUD data route. Before: 500 INTERNAL_ERROR — the ADR-0112 code overwritten, not just the status, so the caller read "the server fell over" instead of "this backend does not implement that declared capability". After: 501 NOT_IMPLEMENTED.

Criterion spelling

The code half reads declaresServerFault from @objectstack/types (PR #6122, pinned by error-leak.test.ts, already read by the analytics route here and by runtime's dispatcher) rather than a fourth open-coded truthiness check. Inside a status >= 500 branch what it contributes is its second half — a non-empty string code — which is what keeps an empty string or a numeric driver errno off the wire dressed as an ADR-0112 code.

Deviation worth a reviewer's eye. The dispatch asked for declaresServerFault as the 5xx passthrough criterion. Gating the status on it would have left the defect alive for a producer that declares a status and no code: declaresServerFault is false there, so such an error would still have its declared 502 re-derived from message text — the exact shape #5437 ruled against one door over — and the two doors would still disagree, just more narrowly. So the predicate gates the code and the band gates the status. A no-code 5xx now passes its status through carrying no code at all, which is the answer resolveErrorResponse already gives that shape and justifies as "No code was declared, so none is invented here — ADR-0112 says the PRODUCER names the condition" (rest-5xx-message-sanitization.test.ts). Full parity, and nothing invented.

Tests

Local: @objectstack/rest 80 files / 1308 tests pass; typecheck clean; eslint clean on the changed files; check:nul-bytes, check:error-code-casing, check:route-envelope, check:empty-changeset green.

CI caught one thing the local run structurally could not, which is worth recording: check:type-check-debt re-measures this package with the test exclusion lifted, and the package's own pnpm typecheck reads a tsconfig that excludes *.test.ts — so the ledger went 155 to 157 (+2) while local typecheck was clean. Both were in the new file (a missing .js import extension under nodenext, TS2835; an implicitly-any spy callback, TS7006), fixed rather than ratcheted; the layer measures 155 again and both gate jobs are green.

Reverse verification — predicted before running, one prediction wrong

Variant: restore the 4xx-only gate, keep the new tests.

section predicted measured
§1 §2 §5 (declared 5xx, half-declarations, route walk) RED RED — 19 failures; #5907's 501 NOT_IMPLEMENTED survives instead of degrading to 500 INTERNAL_ERROR fails by name
§3 (sanitization) GREEN MIXED — 5 of 9 red
§4 (4xx half, branches above the passthrough) GREEN GREEN

The §3 prediction was wrong and is recorded as measured rather than rewritten to fit. It was right about what those cases defend and wrong about which assertion carries it: the containment half (not.toContain(message)) is genuinely direction-insensitive and stayed green in all 9 — the pre-fix terminal branch sanitised too (#5489) — while the envelope half moves, because the old heuristics answered some of those inputs with a different sanitised envelope (raw SQL and driver dumps became DATA_STORE_FAULT's DATABASE_ERROR; a unique-constraint payload became 409 UNIQUE_VIOLATION).

That is worth knowing rather than papering over: for a producer that declared a 5xx, this branch now takes precedence over those text classifiers. Intended — a declaration outranks a keyword guess about the same error, and resolveErrorResponse's door is likewise the first one — and no live producer declares a 5xx with unique-violation or missing-relation text. Undeclared errors are untouched, which the revert made worth pinning explicitly: a new case walks all three dialects' unique violations (SQLite / Postgres / MySQL, driver code and no status) and asserts they still answer 409 UNIQUE_VIOLATION (#6250).

The fix was taken out with git checkout origin/main -- packages/rest/src/rest-server.ts and restored from a saved patch; the restored file is byte-identical (md5sum -c OK).

Follow-up, deliberately not in this PR

packages/drivers/driver-sql/src/sql-driver.ts's uncompilableAggregateFunctionError docblock records the measured consequence this PR removes — "on the /data routes mapDataError's generic status passthrough is 4xx-ONLY, so this declared 501 does not survive to the wire" — and points at #5582. It is now stale in the reader's favour only if they follow the pointer. Left untouched because the dispatch scoped drivers out; a comment-only correction is a three-line follow-up.


Generated by Claude Code

claude added 2 commits August 10, 2026 09:52
…ta routes

`mapDataError`'s explicit-status passthrough accepted 4xx only, while
`resolveErrorResponse` — the door every metadata/UI/discovery/batch route
reports through — accepts 400-599. One declared error therefore got two
answers depending on which door caught it, and on the ~11 direct data-route
call sites the declaration lost: the status was re-derived from the message
TEXT, or fell out of `UNCLASSIFIED_FAULT` as `500 INTERNAL_ERROR`.

Live since #5907: driver-sql / driver-turso throw `status: 501` /
`code: NOT_IMPLEMENTED` for a spec-declared aggregate function the backend
cannot compile, and the caller was told the server had fallen over instead.

The gate is now 400-599 on both doors, with #5437's ruled disposition for a
declared server fault — keep the status, keep the `code`, drop the prose. The
`code` half reads `declaresServerFault` (`@objectstack/types`, PR #6122) so an
empty or non-string code is not mistaken for an ADR-0112 declaration, and
nothing is invented when the producer named no code.

Fixes #5582

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0158ZQo7LiHSxGWpYKuPq1wu
@vercel

vercel Bot commented Aug 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 10, 2026 10:05am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/rest.

9 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/connect-mcp.mdx (via @objectstack/rest)
  • content/docs/api/error-handling-server.mdx (via @objectstack/rest)
  • content/docs/api/index.mdx (via @objectstack/rest)
  • content/docs/permissions/authentication.mdx (via @objectstack/rest)
  • content/docs/permissions/system-context.mdx (via packages/rest)
  • content/docs/plugins/index.mdx (via @objectstack/rest)
  • content/docs/plugins/packages.mdx (via @objectstack/rest)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/rest)
  • content/docs/protocol/kernel/i18n-standard.mdx (via packages/rest)

3 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx (via @objectstack/rest)
  • content/docs/releases/v12.mdx (via @objectstack/rest)
  • content/docs/releases/v17.mdx (via @objectstack/rest)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Aug 10, 2026
CI's `check:type-check-debt` re-measures @objectstack/rest with the test
exclusion lifted, which the package's own `pnpm typecheck` cannot see: 155
recorded, 157 measured (+2), both from the new file — a missing `.js` import
extension under nodenext resolution (TS2835) and an implicitly-any spy callback
(TS7006). Fixed rather than ratcheted; the layer measures 155 again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0158ZQo7LiHSxGWpYKuPq1wu
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

2 participants