Skip to content

analytics 的 filter 拒收到不了调用方:service 侧多数拒收没有 ADR-0112 信封,REST 面又用 message 正则嗅探,一律答 500 #5352

Closed

Description

@os-zhuang

实现 #5334(analytics 第五道门:where 数组下沉)时路过,不属于该单范围面,按 Prime Directive #10 单独记在这里,unassigned。

一个缺陷的两半

作者写错一条筛选(拼错算子、{field: {}}、无法下沉的数组),analytics 会响亮拒收 —— 这是 #3948/#5240/#5325/#5334 一路建立的姿态,正确。但这个拒收到不了调用方:它以 500 ANALYTICS_QUERY_FAILED 落地,而不是 ADR-0112 的 400 INVALID_FILTER。两半各自成立,单修一半都不够。

半 A —— 消费面:/analytics/dataset/query 不读 code/status,改用 message 正则

packages/rest/src/rest-server.ts(analytics dataset 路由的 catch):

} catch (error: any) {
    const msg = String(error?.message ?? error ?? '');
    if (/not declared in the dataset|not backed by a declared relationship|.../.test(msg)) {
        return res.status(400).json({ code: 'DATASET_INVALID', message: msg.slice(0, 1000) });
    }
    logError('[REST] Analytics dataset query error:', error);
    res.status(500).json({ code: 'ANALYTICS_QUERY_FAILED', error: msg.slice(0, 500) });
}

被抛出的错误携带的 err.code / err.status 被丢弃;是否 400 由一串写死的 message 子串决定。ADR-0112 的整个意义是错误自带机器可读的语义码,这里把它换成了字符串嗅探 —— 措辞一改,分类就变,而措辞在这一族单子里几乎每周都在改。

半 B —— 生产面:filter-normalizer.ts 的多数拒收是裸 Error

packages/services/service-analytics/src/strategies/filter-normalizer.ts 里已有的四处拒收都是 throw new Error(...),不带 code/status:

对照:driver-sql、driver-memory、driver-mongodb 的同类拒收全部走 unsupportedFilterError → INVALID_FILTER / 400(ADR-0112),#5334 新落的数组拒收也是。所以同一个作者错误,走 find() 得 400,走图表得 500。

现象

dashboard widget 的筛选里拼错一个算子 → 前端看到 500 + ANALYTICS_QUERY_FAILED,读作「平台炸了」,而不是「你的筛选写错了」。运维告警按 5xx 计。#5334 之后新增的无法下沉数组拒收同样落进这条路:service 侧带着正确的 INVALID_FILTER / 400,REST 面把它降级成 500。

建议(供分诊,不代表已定)

顺序上先 B 后 A,或一并:B 让 analytics 的 filter 拒收统一走一个 unsupportedFilterError 等价物(该文件里 #5334 已落了 invalidFilterError,四处沿用即可);A 让 REST 面先读 error.status / error.code,正则名单退化成没有信封时的兜底(或直接删掉,等 B 落地后)。

单修 A:今天只有 #5334 那一处拒收带信封,其余仍是 500。单修 B:REST 面照旧嗅探 message,信封仍被丢弃。

未验证

没有测量真实 dashboard 里作者写错筛选的频率;严重度请按 triage 定。搜过 open issues(filter-normalizer / INVALID_FILTER / ADR-0112 analytics / ANALYTICS_QUERY_FAILED),没有已存在的同题单。

关联:#5334(第五道门,本发现的来源)、#5240、#5325、#3948、ADR-0112。

Activity

  1. added
    bugSomething isn't working
    domain:servicesRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landed
    on Aug 5, 2026
  2. self-assigned this
    on Aug 5, 2026
  3. os-zhuang commented on Aug 5, 2026

    @os-zhuang
    ContributorAuthor

    认领 + 派发,A 与 B 一并做(engine/services 车道 PM,会话 session_01Pbu27iNUfQCHeuS551Rqo7)

    分支 claude/issue-5352-analytics-filter-refusal-envelope,worktree /home/user/objectstack-issue-5352,域 domain:services,标 target:v17。

    为什么 A 和 B 必须一起做

    正文自己给了答案:单修任何一半都等于没修。 单修 A(REST 面读信封),而 B 的四处拒收仍是裸 Error,那读到的 code/status 是 undefined,照旧落 500;单修 B(拒收带信封),而 A 仍在嗅探 message,信封照旧被丢弃。所以不拆。

    半 B 优先落,理由是它决定 A 能不能删名单

    先让 filter-normalizer.ts 既有的四处拒收都带上信封 —— #5334 已经在同一文件落了 invalidFilterError,四处沿用即可,不要另造。四处是:

    B 落地后,A 那串写死的 message 正则才有可能退化成兜底或直接删掉。

    半 A:REST 面先读信封,正则名单降级为兜底

    packages/rest/src/rest-server.ts 的 analytics dataset 路由 catch:先读 error.status / error.code,有信封就按信封答;正则名单只作为没有信封时的兜底。

    ⚠️ 不要顺手把正则名单整个删掉。 它今天还挡着一批不是 filter 拒收的错误(not declared in the dataset、not backed by a declared relationship 等,答 400 DATASET_INVALID)。那些错误的信封化不在本单范围 —— 名单留着当兜底,并加注释写明它是过渡态、指向本单。若你判断名单里某几条其实已经带信封,逐条给证据再删,不要整批处理。

    这一单的价值要写进 PR

    今晚落的三单拒收(#5325 / #5334 / #5240 在 analytics 侧的部分)在 service 层都是正确的 INVALID_FILTER / 400,但过了这道面全变 500。也就是说:作者在 dashboard widget 里拼错一个算子,看到的是「平台炸了」而不是「你的筛选写错了」,而运维告警按 5xx 计。这一单是让前面那几单真正到达作者的最后一段。

    硬禁区

    测试要求

    • 四处既有拒收各一条:code === 'INVALID_FILTER'、status === 400。
    • 端到端:经 /analytics/dataset/query 打一个拼错算子的筛选,断言 HTTP 400 且 body 的 code 是 INVALID_FILTER,而不是 500 ANALYTICS_QUERY_FAILED。这是本单的验收面,只测 service 层不算数。
    • 回归:正则名单挡的那几类(not declared in the dataset 等)仍然答 400 DATASET_INVALID,没有因为改动而掉进 500 或改码。
    • 真正的内部错误仍然 500:构造一条与 filter 无关的失败,断言它仍是 500 ANALYTICS_QUERY_FAILED —— 证明这次改动没有把所有错误都降级成 400。
    • 反向验证:stash 掉改动,新用例必须失败,原文写进报告。

    Generated by Claude Code

  4. added a commit that references this issue on Aug 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:servicesRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landedpm:dispatchedtarget:v17

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions