fix(rest): 4xx 直通截断超长 message,不再整条换成 "Request failed" (#5423) - #5436
Merged
Conversation
…erasing it (#5423) Both explicit-status passthrough branches in `rest-server.ts` bounded a domain error's message at 500 characters by REPLACING it with the literal 'Request failed' — `status` and `code` landed as usual and every word of the body text disappeared. That inverted the incentive on the whole rejection vocabulary. driver-sql's filter refusals exist only to tell an author which operator or field they got wrong and how the spec declares it, and the two most carefully worded of them (#5158's unlowered FilterArray, #5347's non-boolean $null comparand) are both over the bound — so the more precisely a rejection was written, the more certainly the client read nothing. They were also readable BEFORE they carried a status, through `mapDataError`'s final raw-message fallback: #4436 added `status: 400` to give them an ADR-0112 wire identity and, in this band, cost them their body. An over-long message is now truncated to `slice(0, 499) + '…'` — same shape as the drivers' own `safeShapePreview`. These messages front-load the main clause (operator, field, path, what arrived, what the spec declares) and back-load attribution and issue numbers, which belong in the log. The bound stays at 500; what changed is what happens AT it. Messages under it are byte-for-byte unchanged. `resolveErrorResponse`'s passthrough range is 400-599, wider than `mapDataError`'s; only its 4xx half changes. 5xx keeps the wholesale replacement, matching the sibling branch's recorded "deliberately limited to 4xx ... so internal/SQL details never reach the client verbatim". Also corrects `sql-driver.ts`'s `unsupportedFilterError` docblock, which claimed `status: 400` "makes sendError pass the message through instead of routing it to the SQL-leak heuristic" — the opposite of the measured behaviour. Comment only; no driver behaviour change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016FNvXhtSdnEGEfLEsMmvxh
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 18 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
This was referenced Aug 5, 2026
baozhoutao
marked this pull request as ready for review
August 5, 2026 11:53
baozhoutao
enabled auto-merge
August 5, 2026 11:53
Contributor
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 31003481598 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
This was referenced Aug 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5423
按分诊裁定取 B(截断而非替换)。
前提复核(对
origin/main=123067ce7)issue 的两处行号原样成立,未发生漂移:
packages/rest/src/rest-server.ts:566-569—mapDataError的 4xx 直通,length < 500二分。packages/rest/src/rest-server.ts:785-790—resolveErrorResponse(sendError的取值端),同款二分。一处需要更正 issue 的表述:
sendError那处的直通区间是 400–599,不是 4xx(error.status >= 400 && error.status < 600)。这一点影响了实现取舍,见下。改了什么
抽出一个
truncateClientMessage(),超长时slice(0, 499) + '…',与驱动侧safeShapePreview同源。上限仍是 500,变的是到达上限时的处理方式;短于 500 的消息逐字不变(空/缺失 message 仍降级为Request failed—— 无可截断)。sendError一侧只改 4xx 那一半,5xx 的整条替换逐字保留。理由是这个 4xx/5xx 之分是仓库既有的既定取向,mapDataError同族分支的注释已经写死:「deliberately limited to 4xx: 5xx messages keep going through the sanitizing heuristics ... so internal/SQL details never reach the client verbatim」。4xx 的正文是写给调用方的补救说明;5xx 的正文是服务端故障的日志诊断,恰好在这里够得着而已。放宽 5xx 不在本单范围内,也不该作为搭车项落地。另按裁定改真
packages/plugins/driver-sql/src/sql-driver.tsunsupportedFilterError的注释 —— 仅注释,零行为变更。原文说status: 400让sendError「pass the message through instead of routing it to the SQL-leak heuristic」,与实测相反:不带 status 时原文本就经mapDataError末尾的 raw 分支完整直达(泄漏启发式不命中这些措辞),带上 status 反而进了这道闸门。补齐 issue 的「未验证部分」
sendError分支实际走通了,不是按同款推断:用该文件既有的 in-process harness(RestServer+ mock server +getRoutes()取 handler),让PUT /api/v1/meta/:type/:name真实抛错,读res.json收到的 body。INVALID_METADATA)。顺带一个值得记的实测:按metadata-protocol的真实构造(前 3 条 issue 摘要 +(+N more)),三条 issue 的版本量到 492 字符 —— 距离越线只差 8 个字符。metadata save 不是冷门路径、五条校验错误也不是冷门错误,所以这一族和 issue 猜测的那两条临界过滤器拒收({ field: {} }(零个操作符的字段约束)在同仓有三个答案:driver-sql 组合子内 TRUE、顶层抛 INVALID_FILTER、formula/driver-memory FALSE #5240 ~469、fix(driver-sql,driver-memory,formula)!:{ field: {} }四个后端一律拒收 —— 零个操作符的字段约束不再有三个答案 (#5240) #5327 ~454)一样,是骑在悬崖边上的。这条测量写进了 fixture 的注释里。issues[]、code、status)全程不受影响,有断言。反向验证(方向先判后跑)
预判:把
slice改回整条替换,应当恰好 7 条红 ——mapDataError3 条长消息断言 + 1 条边界(499 逐字 / 500 首次截断)+sendError2 条 +rest.test.ts那条既有 fixture;而全部短消息 / 空消息 / 5xx 断言应当保持绿(它们防的是反向过度修改)。实跑:
Tests 7 failed | 656 passed,失败集与预判逐条一致。Fixture 处置
rest.test.ts:2325既有的guards the passthrough message length (oversized → generic text)正是钉住我要改的那条限的 fixture —— 它把「整条替换」当作意图钉了下来,是这个行为能一直安静存在的原因之一。按整条替换处置:保留它守卫的边界(超长不得整条到达客户端),改为断言截断而非抹除,并注明完整覆盖在新文件里。除此之外全仓无第二处消费方依赖这条限(已 grep)。验证
pnpm --filter @objectstack/rest --filter @objectstack/driver-sql test→ rest44 files / 663 tests全绿;driver-sql63 passed | 4 skipped / 855 passed全绿。pnpm --filter @objectstack/driver-sql typecheck→ Done。@objectstack/rest没有typecheck脚本 —— 它在scripts/check-type-check-coverage.mjs的 DEBT + TEST_DEBT 台账里([P2] framework: 66 个包用 tsup 构建、无人做类型检查 —— 实测 18 个包共 380 处 code-tier 错误(#4118 的 framework 侧对应) #4311),是既有状态,非本次引入。跑了台账本身:check:type-check-coverageOK,62/77 覆盖,未松动。node scripts/check-nul-bytes.mjsOK;改动文件另做了grep -naP控制字符自扫,clean。changeset:
@objectstack/restpatch。🤖 Generated with Claude Code
https://claude.ai/code/session_016FNvXhtSdnEGEfLEsMmvxh
Generated by Claude Code