Skip to content

fix(analytics,rest): 五族 dataset 拒收自带 DATASET_INVALID/400 信封,路由的 message 正则名单收到只剩一条 - #5723

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-5367-dataset-invalid-envelopes
Aug 6, 2026
Merged

fix(analytics,rest): 五族 dataset 拒收自带 DATASET_INVALID/400 信封,路由的 message 正则名单收到只剩一条#5723
os-zhuang merged 2 commits into
mainfrom
claude/issue-5367-dataset-invalid-envelopes

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Refs #5367

注意:本单的第六族(read-scope-sql 十处)按 PM 圈定的范围本轮不实现,逐处分析随报告交给 PM 裁定是否上决策卡。因此正文用 Refs 而不是 Fixes —— 关单与否由 PM 决定。

前提重验(基于 origin/main,行号已漂)

立单时的六条生产方全部仍是裸 throw new Error(...),前提成立。行号如 issue 预告的那样漂了:

issue 里的位置 现在的位置
dataset-compiler.ts:137 dataset-compiler.ts:137(未漂)
dataset-compiler.ts:305 dataset-compiler.ts:305(未漂)
dataset-executor.ts:436 / :596 :438 / :598(+2,#5691)
native-sql-strategy.ts:222 :223(+1)
read-scope-sql.ts 十处 104 / 138 / 144 / 190 / 209 / 217 / 262 / 267 / 272 / 284

rest-server.ts 的正则名单原样在 :6811,#5667looksLikeInternalErrorLeak 收窄在其后的 ③ 分支。

做了什么

新增 packages/services/service-analytics/src/dataset-refusal.ts,导出 datasetInvalidError(message) —— 与同包 filter-normalizer.tsinvalidFilterError(INVALID_FILTER/400)、analytics-service.tsassertMeasureFields/assertDimensionFields(INVALID_FIELD/400)同形,DATASET_INVALID / 400。五处改为经它抛出:

  • dataset-compiler.ts —— v1 无法下降的 aggregate;dimension/measure 走了 include 未声明的 relationship 路径
  • dataset-executor.ts —— order 键不在选中的 dimension/measure 里;totals 分组不是选中 dimension 的子集
  • native-sql-strategy.ts —— join 不在 dataset 声明的 allowlist 里

rest-server.ts 的正则名单删掉对应五条,只剩 /read-scope-sql/,并在原地写清「这是退休时间表的最后一项」以及为什么它单独留着。

落点选择的代码证据:invalidFilterErrorfilter-normalizer.ts 里的非导出 function,五处又跨三个文件(含 strategies/ 子目录),所以复用不了、必须新建一个共享模块;放在同包 src/ 下,只被这三个文件 import,不进 index.ts(不扩公共面)。

DATASET_INVALID 不是新造的 code(#5352 起路由就在答它),但按 ADR-0112 D3 的 provenance 规则,在 ERROR_CODE_LEDGER 里给 @objectstack/service-analytics 也补了一行(union 不变,REGISTERED_ERROR_CODES / ErrorCode / 生成产物全部不变 —— check:generated 10 项全绿可证)。构造器里把 code 标注成 RegisteredErrorCode,所以「没登记的 code」是编译错误,而不是某条路由运行时才拒的 body。

native-sql-strategy.ts:223 动手前的核实(PM 硬性要求)

结论:属调用方错误,可以信封化。

joins 的每一个 alias 都由 qualifyAndRegisterJoin 注册,而在 dataset 路由上能注册出 allowlist 之外的 alias 的输入只有请求自己的:lookupMember 对「点分但没有任何 cube dimension 声明」的成员返回合成 { sql: member },于是 selection.dimensionsselection.timeDimensions 以及 runtimeFilter 里写成 account.name 的成员都会落到这里。dataset 自己的 dimension/measure 到不了:compileDatasetassertDeclared 在编译期就拒了未声明的 relationship 路径(本 PR 里同样是 400 DATASET_INVALID,两者判一致而非分叉),而 resolveMeasureSql 根本没有合成兜底。

唯一的非调用方触发形态是给手写 cube 用的 legacy config.getAllowedRelationships 钩子(那时不匹配属宿主配置错误)—— 但它在 /analytics/dataset/query 上不可达:queryDatasetregisterDataset,getAllowedRelationships 因此总是从 datasetRegistry 作答,不会 fall through 到钩子。这段推理写进了代码注释。

刻意保持裸 Error 的三类(写在 dataset-refusal.ts 头注释,并有 pin 测试)

  1. read-scope-sql.ts 十处 —— 输入是安全服务从管理员策略编译出的 RLS FilterCondition 和编译器生成的 join alias,都不是调用方输入;
  2. 内部不变量 —— 如 dataset-compiler.ts:135「non-derived measure 没有 aggregate」,spec refinement 已保证,到达即我们的 bug;
  3. 我们自己两张表之间的漂移 —— objectql-strategy.ts 的回显 SQL 渲染器已经明确表过这个态(/analytics/sql 回显的 SQL 丢掉 $startsWith / $endsWith 谓词:回显比实际执行的查询更宽,无法复现结果 #5333:"Deliberately NOT invalidFilterError's 400 envelope")。

所以这个模块刻意不是「本包唯一的拒收出口」(那是 invalidFilterErrorfilter-normalizer.ts 才能作出的声明),它是「本包拒收调用方的出口」。

#5366 既有回归用例的逐条改判

三处引用了被删条目,按「这条 fixture 钉的正是我删掉的那根肢体」处理 —— 不是批量重拼措辞:

  1. analytics-filter-refusal-envelope.test.tsFALLBACK 六例 —— 原断言是「裸 Error + 这段措辞 → 400」,改判后再断言它就是在断言缺陷本身。所以整块换掉:read-scope-sql 一例保留(它仍靠名单);另外五族每族钉两个方向 —— 带信封 → 400 DATASET_INVALID(经分支 ①),同一条措辞裸抛 → 500(这一半才是「正则条目真的删掉了」的证据)。
  2. analytics-dataset-dimension-gate.test.ts(fix(service-analytics,rest): analytics dimension 的源字段闸门 —— 不存在的 dimension 答 400 INVALID_FIELD,dataset 500 不再回显 SQL (#5520) #5667)的 ② 半(原本发裸 Error 靠名单)→ 改成发带信封的错误走分支 ①,并新增 ③ 半覆盖仅存的 read-scope-sql 条目。fix(service-analytics,rest): analytics dimension 的源字段闸门 —— 不存在的 dimension 答 400 INVALID_FIELD,dataset 500 不再回显 SQL (#5520) #5667 的 8 例其余部分零改动、全绿。
  3. analytics-routes.test.ts 的「maps a dataset D-C compile error to 400」 → 同一条 message 加上信封;外部断言(400 / DATASET_INVALID)一字未改。

三处的外部可见行为都没变,变的是内部机制;两处需要改造 fixture(裸 → 带信封),一处只需加信封。已按规则消费半径扫过全仓(grep 五条措辞 + DATASET_INVALID),packages/rest 之外没有别的 fixture 依赖这串正则。

新增覆盖

  • packages/services/service-analytics/src/__tests__/dataset-refusal-envelope.test.ts(15 例)—— 照 analytics 的 filter 拒收到不了调用方:service 侧多数拒收没有 ADR-0112 信封,REST 面又用 message 正则嗅探,一律答 500 #5352 那份的两段式:先钉拒收集合没动(每族真实生产方 + 一条「相邻的合法输入仍然编译」的反面控制,改动前后都绿),再钉信封;第三段钉「刻意留裸的」—— read-scope-sql 两例(断言 code/status 仍为 undefined,并在注释里写明它一旦翻转就必须同 PR 删掉路由最后一条)+ 内部不变量一例。
  • packages/rest/src/analytics-dataset-refusal-envelope.test.ts(8 例)—— 五族端到端穿过真实 AnalyticsService(aggregate 路径四族 + raw-SQL 路径一族),精确断言 400 / DATASET_INVALID / message 指名内容 / body 是 4xx 形状(message 而非 error);两条正面控制(两条路径各一条,其中 raw-SQL 那条用 include: ['account'] 让同一个点分 selection 合法,证明 ⑤ 是关于 allowlist 的判决而不是「点分成员一律被拒」)。

反向验证(方向先预测,后实测)

方向 A —— 把五处生产方改回裸 throw new Error(...),重建 service-analytics:

方向 B —— 把五条正则加回去(生产方仍带信封):

  • 预测:analytics-filter-refusal-envelope.test.ts 的「裸 → 500」5 例转红;「带信封 → 400」5 例保持绿(① 在 ② 之前);端到端文件完全保持绿
  • 实测:Tests 5 failed | 49 passed (54),断言原文 AssertionError: expected 400 to be 500,端到端文件全绿。与预测一致。这个不对称值得写下来:端到端文件在设计上测不出被加回的正则条目(信封先答),名单删除只由「裸 → 500」那一半钉住 —— 所以那一半不是冗余断言,它是这个 PR 唯一的名单守卫。

两个方向跑完后已 git checkout 复原并重新验证全绿。

验证

  • pnpm --filter @objectstack/service-analytics testTest Files 54 passed (54) / Tests 954 passed (954)
  • pnpm --filter @objectstack/rest testTest Files 52 passed (52) / Tests 764 passed (764)
  • packages/specerror-code-ledger.test.ts + errors.test.ts + error-catalog-docs.test.ts → 38 passed
  • pnpm --filter @objectstack/spec check:generated → 10/10 up to date
  • pnpm check:error-code-casing → self-test 17 例 + 2982 文件全绿;pnpm check:route-envelope → 8 模块 7 conformant / 0 ratcheted / 1 exempt
  • node scripts/check-nul-bytes.mjs → OK(5618 文件);另按纪律自扫 grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f]' 覆盖本 PR 全部改动文件 → clean
  • pnpm check:type-check-coverage → OK(63/78 + 15 DEBT);两个 DEBT 包直接量:@objectstack/rest tsc --noEmit 2 errors(与 ledger 记的 2 一致)、@objectstack/service-analytics 7 errors,没有一条落在本 PR 新增/修改的文件上
  • 已 merge origin/main(eb26126d5)后重跑上述全部

范围外发现(已按 #4949 先搜重)


Generated by Claude Code

claude added 2 commits August 6, 2026 01:44
…D/400 and shrink the route's message list to one entry (#5367)

`POST /analytics/dataset/query` classified six error families by matching
hardcoded substrings of their message text, because all six producers were
bare `throw new Error(...)`. That made their HTTP status a property of their
wording: a rephrasing with no logic change moved a refusal from 400 to 500
with nothing going red. #5352/#5366 delivered declared/loud/tested for that
accommodation; Prime Directive #12 also requires removable on a schedule.

Five producers now declare the verdict themselves through a new
`dataset-refusal.ts` (`datasetInvalidError`, `DATASET_INVALID`/400, the same
shape as `invalidFilterError`):

- dataset-compiler: unsupported aggregate; undeclared relationship path
- dataset-executor: unselected order key; totals grouping outside the selection
- native-sql-strategy: join outside the declared allowlist

Their five entries are deleted from the route's regex. `read-scope-sql` keeps
its entry on purpose: its ten fail-closed refusals lower an admin-authored RLS
policy, not caller input, so the right code/status is a separate judgement.

`DATASET_INVALID` is registered under `@objectstack/service-analytics` in
ERROR_CODE_LEDGER for provenance (the union is unchanged), and the constructor
types it as `RegisteredErrorCode` so an unregistered code fails `tsc`.
@vercel

vercel Bot commented Aug 6, 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 6, 2026 2:07am

Request Review

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/rest, @objectstack/service-analytics, @objectstack/spec.

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

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/connect-mcp.mdx (via @objectstack/rest)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/data-api.mdx (via @objectstack/service-analytics)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/rest, @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/rest, @objectstack/service-analytics, @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/tenancy-modes.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/service-analytics, @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authentication.mdx (via @objectstack/rest)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/service-analytics, @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/rest, @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/rest, @objectstack/service-analytics, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/rest, @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via packages/rest, @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/rest, @objectstack/service-analytics, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/rest, @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/rest, @objectstack/service-analytics, @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/service-analytics, @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/apps.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

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 6, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review August 6, 2026 02:20
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 6, 2026
Merged via the queue into main with commit 978fed2 Aug 6, 2026
25 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-5367-dataset-invalid-envelopes branch August 6, 2026 02:31
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

Development

Successfully merging this pull request may close these issues.

2 participants