Repository navigation
mapDataError 的显式状态直通只覆盖 4xx,数据路由上一个声明了 502/503 的生产者拿不回自己的状态码(与 resolveErrorResponse 不对等) #5582
Description
Activity
发现分诊轮判级:持有(留
finding,补domain:cli域标)。- 理由:正文自证无活体生产者 —— 仓内声明 5xx 且走到
mapDataError直调点的组合今日不存在(ERR_DATASOURCE_UNAVAILABLE有自己的 503 分支,meta 路由走resolveErrorResponse那道门),今天没有用户撞上;且同领地/meta/:type上一个未分类的服务端故障被报成 HTTP 400 —— handleRouteError 的兜底把 outage 说成客户端错误 #5489 在飞(assignee 活跃),正文点名的两条 pin 测试就在其文件面内,现在入队必然同文件撞车。 - 重启条件:
/meta/:type上一个未分类的服务端故障被报成 HTTP 400 —— handleRouteError 的兜底把 outage 说成客户端错误 #5489 落地后复核直通分支是否已顺带对齐;未对齐且出现活体 5xx 数据路由生产者(或维护者裁mapDataError与resolveErrorResponse合流)时晋级。
本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
- 理由:正文自证无活体生产者 —— 仓内声明 5xx 且走到
发现分诊轮(#4949 纪律):维持持有(留
finding),域维持domain:cli。上一轮重启条件的第一个子句本轮已结算,记录如下,后续轮次不必再测这一半。重启条件复核(条件为合取,只满足了一半)
上一轮(08-05 20:00Z)写的是:「#5489 落地后复核直通分支是否已顺带对齐;未对齐且出现活体 5xx 数据路由生产者(或维护者裁
mapDataError与resolveErrorResponse合流)时晋级」。/meta/:type上一个未分类的服务端故障被报成 HTTP 400 —— handleRouteError 的兜底把 outage 说成客户端错误 #5489 已 closed/completed ⇒ 复核触发;- 结论:未对齐(
origin/main@9e3709a实测)。mapDataError的直通分支仍是error.status >= 400 && error.status < 500(packages/rest/src/rest-server.ts:723),而resolveErrorResponse一侧的 400–599 判据仍在(:1020,status >= 400 && status < 600)—— 两道门对「生产者声明了 status」依旧给两个答案; - 正文点名的两条 pin 原样健在:
rest.test.ts:2328(“does NOT pass through an explicit 5xx status”)、rest-4xx-message-truncation.test.ts:145(“5xx never enters this branch at all”)⇒ 改动仍需一并重写这两条; - 第二个子句仍不成立:活体 5xx 生产者本轮重测为零 ——
packages/objectql/src、packages/drivers/*/src、packages/metadata*/src、packages/services/*/src下status = 50x/status: 50x(非测试)零命中,阳性对照同一扫描面的status: 4xx有命中(如objectql/src/overlay-precedence.test.ts:209的 403 一族),证伪「扫描器坏了」。
因此:持有,重启条件收窄为单条
#5489 是否已对齐这一问已答完(没有),⛔ 后续轮次不要重复测。唯一剩下的晋级触发点:出现一个声明 5xx 且能走到 CRUD 数据路由mapDataError直调点的活体生产者(那时它是活体状态码降级,直接入队),或维护者裁定mapDataError与resolveErrorResponse合流(那是两道门收敛的契约取舍,走决策卡)。在此之前它仍是「今天没有用户撞得到的静默降级」。本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
Findings triage round (#4949 discipline): HOLD maintained (
findingkept), domain staysdomain:cli. Stale-premise check @origin/main80f7dc6. Two real updates below — the cost model of the fix changed, and clause 2 was re-measured against a file that moved.1. The divergence itself: unchanged
packages/rest/src/rest-server.tsmoved since the last grading —64cd0108(#5811 / PR #6122, 2026-08-07T02:54Z, "/analytics/query no longer echoes RLS policy field names") — so this was re-read rather than assumed:mapDataErrorpassthrough is still 4xx-only:error.status >= 400 && error.status < 500(:728);resolveErrorResponseis still 400–599 (:1025);mapDataErrorstill has 11 direct call sites in the file (the CRUD data routes that bypassresolveErrorResponse).
⇒ the two doors still give two answers to "the producer declared a status". Body premise intact.
2. NEW — there is now a third answer, and it is a named, tested predicate
PR #6122 promoted the withhold criterion into
@objectstack/typesasdeclaresServerFault(err)(status >= 500+ non-emptycode), with two consumers reading it:rest-server.ts:7001(the analytics dataset route) andruntime/src/dispatcher-plugin.ts:500.That matters to this issue in one specific way: the fix this body proposes ("make
mapDataError's passthrough matchresolveErrorResponse: 4xx truncate the wording, 5xx keep the status, drop the wording, keep thecode") now has an existing spelling — the same predicate, already pinned bypackages/types/src/error-leak.test.ts. The change is no longer "invent a criterion and defend it"; it is "read the criterion the repo already agreed on at two other boundaries". The two pins named in the body (rest.test.ts:2328,rest-4xx-message-truncation.test.ts:145) still have to be rewritten, so the cost is not zero — but it dropped.3. Clause 2 re-measured (a live 5xx producer) — still NOT satisfied, and now for a sharper reason
The scan found something the 08-06 round reported as zero: a non-test producer that declares a 5xx with a code now exists —
packages/services/service-analytics/src/read-scope-sql.ts:166-172—readScopeCompileError()setscode = READ_SCOPE_COMPILE_FAILED,status = 500.
But it does not reach this issue's failure mode. It exits through the analytics faces —
rest-server.ts:7001(a hand-builtres.status(500).json(...)) anddispatcher-plugin.ts:500— not through any ofmapDataError's 11 direct call sites on the CRUD data routes. Also re-checked, unchanged from the 08-06 reading:metadata-protocol/src/protocol.ts:1162(503),:6224(501),:10546(500) are meta-route producers ⇒resolveErrorResponse's door. Positive control (per Operational notes 6, a zero reading needs one): the same scan surface does return 4xx producers, e.g.service-analytics/src/analytics-service.ts:1622(status: 404/CUBE_NOT_FOUND) — the scanner is not broken.⇒ still no producer that declares 5xx and can reach a CRUD data route. Today's users still cannot hit this.
4. Verdict: HOLD — restart conditions, one refined
- (unchanged) A live producer declaring 5xx that reaches one of
mapDataError's direct call sites on the CRUD data routes ⇒ that is live status-code degradation, promote topm:queueimmediately. - (refined) Or a maintainer ruling that
mapDataErrorandresolveErrorResponseconverge — a public-contract trade-off, so a decision card. What changed: the implementation of that ruling is nowdeclaresServerFault, not a fresh predicate; whoever writes the card should say so, because it is most of the argument for the cheap side. - ⛔ Do not re-measure "did
/meta/:type上一个未分类的服务端故障被报成 HTTP 400 —— handleRouteError 的兜底把 outage 说成客户端错误 #5489 align the passthrough?" — answered on 08-06: no, and fix(runtime,types)!:/analytics/query不再回显 RLS 策略字段名 —— 声明式 server-fault withhold 提升为两个边界共享 (#5811) #6122 did not align it either. Both readings are now on the record; re-running them buys nothing.
本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
这条不再是观察类了:#5907 的 PR 造出了第一个活体生产者。
本单正文说「仓内声明 5xx 且能走到数据路由直调点的生产者,我没有测到活体」。#5907(聚合函数拒收信封,两驱动同改)按车道裁决给能力缺口类条件取了 ADR-0112 标准目录的
NOT_IMPLEMENTED/ 501:packages/drivers/driver-sql/src/sql-driver.ts—mapAggregateFunc对count_distinct/array_agg/string_agg(spec 已声明、SQL 后端编不出)packages/drivers/driver-turso/src/remote-transport.ts— 同一条件的远程半边
这三个函数过得了协议门(它们在
AggregationFunction枚举里,metadata-protocol的#4254形状校验放行),所以它们是从驱动一路抛到数据路由的 —— 正是本单描述的直调点。走mapDataError的结果:驱动抛出 status=501 code=NOT_IMPLEMENTED mapDataError 4xx 直通分支 跳过(501 不在 400–499) 文本启发式 全部不命中 终局兜底 UNCLASSIFIED_FAULT → 500 { code: 'INTERNAL_ERROR' }即状态码与
code一起丢失:声明的NOT_IMPLEMENTED被换成INTERNAL_ERROR,调用方读到的是「服务器炸了」,而不是「这个后端没实现这个已声明的能力」。比本单正文测到的 502→500 更重一档 —— 那次只丢状态码保真度,这次连 ADR-0112 的语义码本身都被覆写。对分诊的两点补充:
- 修法不变,仍是本单给出的那条:把
mapDataError的直通改成与resolveErrorResponse同款(4xx 截断措辞、5xx 保状态丢措辞留code)。本单已经点名的两条 pin(rest.test.ts的does NOT pass through an explicit 5xx status、rest-4xx-message-truncation.test.ts的5xx never enters this branch at all)需一并重写。 - drivers(sql,turso):
Unsupported aggregate function两面都是裸Error(code/status 皆 undefined),且三个 spec 已声明的聚合函数无任何 SQL 后端编译 #5907 不夹带这一修:它落在packages/rest,与本单的驱动面零重叠,而且驱动侧该做的事(在抛出点如实声明code/status,ADR-0112)已经做完 —— 消费侧把它压平是这一单的领地。drivers(sql,turso):Unsupported aggregate function两面都是裸Error(code/status 皆 undefined),且三个 spec 已声明的聚合函数无任何 SQL 后端编译 #5907 的uncompilableAggregateFunctionErrordocblock 已把这个实测后果记在原地,并指回本单,免得下一个读者当成驱动的 bug 重新排查一遍。
Generated by Claude Code
os-project-manager commented
on Aug 8, 2026 CollaboratorMore actionsFindings sweep (maintainer-authorized one-off, 2026-08-07 — registered on #6015): promoted to the queue. Restore-invariant: the repo already ruled (#5437 / PR #5464) that declared 5xx statuses pass through
resolveErrorResponse;mapDataError's 4xx-only branch breaks that parity on every CRUD data route. No live producer yet is why it sat as a finding — but the two doors disagreeing on the same declaration is the defect, not the traffic.finding→pm:queue.
Generated by Claude Code
Claim: PM loop round 5 (cli lane) — queued by the maintainer-authorized 2026-08-07 findings sweep (comment 5223600933); the same-file serial constraint cleared today (#6349→PR #7372 merged
2443bb4c4, #7035→PR #7293 merged earlier — no open PR touchesrest-server.tsat dispatch time).
Session:session_0158ZQo7LiHSxGWpYKuPq1wu
Branch:claude/issue-5582-mapdataerror-5xx-passthrough
Worktree:objectstack-issue-5582
Domain:domain:cli(as labeled)
File surface:packages/rest/src/rest-server.ts(mapDataErrorpassthrough branch), the two pins the body names (packages/rest/src/rest.test.ts~:2328 "does NOT pass through an explicit 5xx status",packages/rest/src/rest-4xx-message-truncation.test.ts~:145 "5xx never enters this branch at all"), new coverage, changeset. Criterion spelling: the existingdeclaresServerFaultpredicate from@objectstack/types(per the 08-07 triage reading — read, not re-invented). ⛔ No driver-side edits: the drivers already declare truthfully (ADR-0112, #5907); the flattening is consumer-side.
Container & model: M card,mode:subagent,model: opus
Serial constraints cleared: in-flight #6878/PR #7396 isplugin-hono-server+qa/http-conformance— file-disjoint; queue entries pr-7304/7306/7379/7187/7201 none touchpackages/rest.
Live-producer note for the implementer: since #5907, driver-sql/driver-turso throwstatus=501 code=NOT_IMPLEMENTEDforcount_distinct/array_agg/string_aggand that reachesmapDataError's direct call sites today — the fix's acceptance case should show 501/NOT_IMPLEMENTEDsurviving instead of degrading to 500/INTERNAL_ERROR.
Generated by Claude Code
ACCEPT — PR #7402 reviewed and being driven to landing. All 26 check runs green (ESLint and TypeScript Type Check conclusions verified directly, not from the report).
What shipped:
mapDataError's explicit-status passthrough widened to 400–599 — the same doorresolveErrorResponseopens — with #5437's ruled 5xx disposition: keep the declared status, keep the machine-readablecode, drop the prose unconditionally. The 4xx arm (truncation,objectretained, #5423) is untouched. The live #5907 producer now surfaces501 NOT_IMPLEMENTEDon the CRUD data routes instead of degrading to500 INTERNAL_ERROR; both pins the body named were rewritten to the new contract, and a new 28-case suite covers the band, half-declarations, sanitization, and three in-process route walks.The dev corrected the dispatch, and the correction stands — acknowledged in the open. The dispatch said to gate the 5xx passthrough on
declaresServerFault; the dev gated the status on the band and used the predicate for the code half only, with measured grounds:declaresServerFaultrequires a non-empty code, so predicate-gating the status would have left a no-code declared 502 still re-derived from message text — the exact #5437 shape — and both original pins are built on a no-code 502.resolveErrorResponseanswers that shape with status preserved and no code invented ("the PRODUCER names the condition"), andmapDataErrornow matches it exactly. That is full parity, which was the ruled intent.Ordering decision (PM's call under the escalation bar, veto window open): the dev's open question — should a declared 5xx outrank the text classifiers below — is resolved as A, as shipped. Grounds:
resolveErrorResponse's door likewise runs first, #5437 ruled against a declared status being overwritten by a keyword heuristic, and option B would re-create precisely that defect for the inputs the heuristics recognise. No live producer declares a 5xx with classifier-recognisable text (scanned), and the new §4 case pins the live half: undeclared driver conflicts still answer409 UNIQUE_VIOLATIONacross all three dialects. This is recorded here rather than escalated because existing rulings determine the answer; the maintainer can veto by commenting.Follow-up filed as #7407 (driver docblock paragraph falsified by this PR — comment-only,
finding, deliberately unqueued).
Generated by Claude Code
- added 4 commits that reference this issue
on Aug 17, 2026 - added a commit that references this issue
on Aug 24, 2026 - added a commit that references this issue
on Sep 1, 2026 - added a commit that references this issue
on Sep 6, 2026 - added a commit that references this issue
on Sep 9, 2026
发现于 #5489 的兜底测绘,不在该单文件面内,按 Prime Directive #10 单独立项。观察类:我是用注入错误测到的,没有找到会走到这条组合的活体生产者,严重度请分诊时判定。
事实
packages/rest/src/rest-server.ts有两道错误门,对「生产者声明了status」这件事给的判断不一样:resolveErrorResponse(sendError/handleRouteError走的那道)接受 400–599,5xx 保留声明的状态码、丢措辞、留code(sendError 的显式状态直通覆盖 400–599,5xx 的原始驱动报错绕过全部泄漏启发式直达客户端(metadata-protocol 有活体产出方) #5437 / PR fix(rest): a declared 5xx no longer ships its own message to the client (#5437) #5464)。mapDataError自己的直通分支只接受 4xx(error.status >= 400 && error.status < 500)。5xx 落下去继续走文本启发式。而 CRUD 数据路由是直接调
mapDataError的(const mapped = mapDataError(error, req.params?.object),rest-server.ts 约 4804 / 4839 / 4894 / 4956 / 5034 / 5076,另有 6049 / 6462 / 6612 三处),不经过resolveErrorResponse。所以同一个声明了status: 502的错误:实测(#5489 的兜底测绘,给终局兜底加桩跑
@objectstack/rest全套):这个错误没有命中任何文本启发式,一路落到终局兜底。#5489 之前它以 400 携带主机与端口原文下发;#5489 之后它是
500 INTERNAL_ERROR(已消毒、在正确的段位)。残留的只有状态码保真度:声明的 502(「上游依赖不可达,可重试、可换节点」)被压成 500。为什么值得记一笔
isExpectedDataStatus把 502/503 当正常生命周期结果(不打 unhandled 日志),500 不是;代理与重试策略也区别对待。ERR_DATASOURCE_UNAVAILABLE有自己的 503 分支;metadata-protocol 的两个 overlay 500 走的是 meta 路由,即resolveErrorResponse那道门)。所以今天没有用户会撞上;但它是一条mapDataError被直调时就生效的静默降级。可能的修法(供分诊)
把
mapDataError的直通改为与resolveErrorResponse同款:4xx 截断措辞、5xx 保状态丢措辞留code。注意rest.test.ts的does NOT pass through an explicit 5xx status与rest-4xx-message-truncation.test.ts的5xx never enters this branch at all两条 pin 是按现状写的,改动需一并重写(#5489 已把这两条从纯否定断言升级为钉住实际落点,便于下一手对照)。相关:#5489(本单发现来源,已把终局兜底改为消毒 5xx)、#5437 / PR #5464、#5462 / PR #5530、#5532(同一片领地的另一半:
getMetaItem的裸 catch 把存储故障吞成 not found)。