fix(service-analytics): dataset 降级路径先读 ADR-0112 信封,再读措辞 (#5717) - #6045
Merged
Conversation
…12 envelope before the message (#5717) `queryDataset`'s catch degrades to an empty result for the #5033 case (a widget whose backing object is not mounted in this kernel). Its criterion was `isMissingSourceError` — a substring match over the error MESSAGE — so the leniency was available to any error phrased like a driver, and the outcome was not a wrong status code but a silent empty result: no exception, no 4xx, no 5xx, one warn and a confident empty chart. `dataset-compiler.ts`'s "…includes relationship "R" which does not exist on object "O"." already matched (both `relation`, inside "relationship", and `does not exist`). It has never gone off only because the compile point sits outside the try — a mine, wired and unarmed. Two independent defences: - an error carrying an ADR-0112 envelope (numeric `status` + non-empty `code`, the same structural fact `rest-server.ts`'s analytics catch reads) is re-thrown untouched, ahead of any message inspection. Status range is deliberately not part of the test: a declared 5xx is if anything worse to swallow than a 400; - the postgres limb is anchored to postgres's actual wording (`relation "x" does not exist`) instead of "any sentence with both words" — the pattern the sibling `missingSourceRelation` already used. Measured over the 13 real wordings this repo carries, exactly one verdict moves and it is that compiler refusal; no driver wording changes, so #5033's leniency for bare driver errors is untouched (asserted in all four reverse-verification states, not merely claimed). The compile point deliberately stays outside the try — moving it in would newly expose the compiler's bare invariants and the host-supplied relationship resolver to this degradation path. Residue filed as #6035 (postgres's write-path missing-COLUMN wording carries a whole missing-relation phrase inside it; dormant on a read-only face). Fixes #5717 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015a5qkLzpGXhLL2F5gvJ7dD
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 8 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
hotlong
marked this pull request as ready for review
August 6, 2026 17:49
This was referenced Aug 6, 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 #5717
前提复核(立单行号已过期,以 origin/main
b5bdf48现状为准)isMissingSourceErroranalytics-service.ts:92:795(isMissingSourceError判据在:796)dataset-compiler.ts:281-284,且 #5963 后自带DATASET_INVALID/400:722(registerDataset),try 在:793才开始前提全部成立,
premise_still_valid: true。改了什么(范围 B + C,均落在
analytics-service.ts一个文件)B(主判据) —— catch 里新增优先分支:带 ADR-0112 信封的错误原样上抛,先于任何读措辞的逻辑。
信封判据抄仓内既有惯例、不发明第二种:
rest-server.ts的/analytics/dataset/querycatch 用的就是typeof status === 'number'+ 非空字符串code(envelopeStatus/envelopeCode)。状态码区间刻意不入判据,这一点与派发令里的示例(
status小于 500)不同,理由写在代码注释里:400 那一支是本单的明面(一条DATASET_INVALID/400 变成空网格),但已声明的 5xx(read-scope-sql.ts的READ_SCOPE_COMPILE_FAILED/500 —— fail-closed 的 RLS 下推)被吞掉只会更糟:一个服务端故障被画成一张自信的空图,没有任何人被告知。两者的共同事实是「生产者已经回答了分类问题」,措辞启发式没有资格重新打开它。issue 正文 B 的原话也正是「降级路径只对不带 ADR-0112 信封的错误生效」,4xx 是其动机实例而非判据本身。C(可选支,做了) —— postgres 那一支从
includes('relation') && includes('does not exist')收紧为锚定 postgres 真实措辞的/relation\s+["']?[A-Za-z0-9_$.]+["']?\s+does not exist/i,与同文件里已经存在的兄弟函数missingSourceRelation用的是同一条模式(metadata/src/utils/schema-sync-errors.ts也是这个形状)。收紧后「是不是缺了什么」与「缺的是什么」这两个函数不会再在这一支上互相矛盾。C 的量化(实测,13 条仓内真实措辞)
no such table: trelation "t" does not existrelation "public.acct" …column "c" of relation "t" …column "c" does not existTable 'app.t' doesn't existDatasource '…' … is not registered.Object 'x' is not registeredCUBE_NOT_FOUND(#3867)nested/relation value …13 条里只有 1 条改判,就是那条雷;没有任何真实驱动措辞改判 —— 这正是「收紧而非改变 #5033 行为」的证据。
为什么不把编译点移进 try(实测后的判断)
派发令把这一步留给实测。结论:不移,并说明未覆盖面。
移进去会让
registerDataset路径上的裸错误新落入降级面:dataset-compiler.ts:135的内部不变量(non-derived measure … has no aggregate)、以及宿主提供的relationshipResolver/getObjectDatasource/isExternalObject三个回调抛出的任何东西 —— 其中宿主回调抛Object 'x' is not registered一类会新被吞成空网格。那是把宽容度朝与本单相反的方向扩大(「Absence must be loud」/「prefer failing to falling back」),而本单要的是收窄。雷本身不需要靠移编译点来拆:测试里直接把真实 producer 造出来的那个错误对象从 try 内部抛出(见下),这与「编译点被移进来」在 catch 看来是同一件事,却不引入上面那片新面。
测试
新增
packages/services/service-analytics/src/__tests__/dataset-degradation-envelope.test.ts(11 例),两块:CUBE_NOT_FOUND/404(assertInferableCube,消息里含「is not a registered object」,收紧后仍然命中嗅探器 —— 这一例专门隔离 B,任何措辞收紧都救不了它);READ_SCOPE_COMPILE_FAILED形状,措辞为合成,注释里写明为什么必须合成);compileDataset真跑出来的错误,含DATASET_INVALID/400),从 try 内部抛出 —— 雷被正式拆除的直接断言。外加同一措辞剥掉信封的裸 Error 一例:那一例只有 C 能救。no such table、postgres 真实措辞、mysql 措辞照旧降级为{rows: [], fields: [], totals: []}+ warn;裸的「join 表缺失」照旧响亮报跨库拓扑错误;裸的语法错误照旧上抛。反向验证(方向在跑之前就写死了,并且是分裂的)
预测:两条防线互相独立,单独回退任何一条,另一条仍然接住 compiler 那一例 —— 所以「回退就全红」在这里是错误期待。实测(逐个回退,11 例):
CUBE_NOT_FOUND、已声明 5xx最后一行我第一版预测写的是 3 红(两个单退集合的并集),实测 4 —— 差的那一例正是雷本身。测试文件头把这个更正留在原处而不是悄悄改掉,因为那第四行才是这一单的发现。四种状态下 #5033 的五条用例全绿,这就是「刻意宽容未被触动」的证据形态。
验证记录(全部前台阻塞执行,持容器级
flock锁,--max-old-space-size=4096)pnpm --filter @objectstack/service-analytics exec vitest run --maxWorkers=2→ 61 files / 1127 tests passedpackages/rest消费半径(路由侧读信封的 7 个用例文件:analytics-dataset-refusal-envelope/analytics-dataset-unlisted-refusal-envelope/analytics-dataset-dimension-gate/analytics-dataset-where-gate/analytics-filter-refusal-envelope/analytics-read-scope-refusal-envelope/analytics-routes)→ 7 files / 84 tests passedlint.yml逐个枚举后跑过的门:pnpm lint(全仓 ESLint)、check:nul-bytes、check:doc-authoring、check:adr-anchors、check:route-envelope、check:error-code-casing、check:wildcard-fallthrough、check:durability-log-level、check:startup-registry-verdict、check:engine-double-contract、check:slot-lookup、check:query-options-erasure、check:release-notes、check:type-check-coverage—— 全 PASS(该包在 DEBT 账本里冻结 3 个错误,新测试文件没有改变计数)grep -naP扫三个改动文件的控制字节区间 → 干净未触:
packages/spec、drivers、buildQuery粒度判据(#6003 面)、各拒收 throw 本体(#5963 面)。必答项:对 #6007(compareTo 合并键错位)的影响
完全无影响,与预期一致。#6007 落在
dataset-executor.ts的 compareTo 行合并逻辑(合并键构造),本 PR 只改analytics-service.ts里queryDatasetcatch 的分类顺序与嗅探器的一条正则;两者不共享函数、不共享数据流。唯一的接触面是「若 compareTo 路径抛datasetInvalidError,它现在保证不被降级」—— 那是本 PR 给 #6007 所在文件的保障,不改变 #6007 要修的合并行为,既不使其变简单也不使其变难、更不使其变得不必要。出界发现
isMissingSourceError仍把 Postgres 的「缺列」措辞column "c" of relation "t" does not exist判为「缺源」——文档明说不该,今天靠读路径不产生该措辞而无害 #6035(finding,未派单):postgres 写路径的缺列措辞column "c" of relation "t" does not exist内含一整段缺表措辞,收紧前后都命中嗅探器,而嗅探器自己的文档写明「不含列/语法错误」。只读面上不产生该措辞(SELECT 说的是column "c" does not exist,不含relation),属 observation 类;仓内已有先例(rest-server.ts:825先摘缺列再判缺表)。本 PR 在isMissingSourceError的注释里指名了这条残留与 issue 号,不在本单顺手改 —— 它超出 C 的既定范围(C 是「只匹配 postgres 真实缺表措辞」,而这条措辞字面上就是它)。Generated by Claude Code