现象
analytics 查询里,一个引用了不存在字段的 dimension 会一路走到 SQL,被驱动以 no such column 打回,调用方拿到 500 —— 裸 driver 错误码上线,dataset 面还把生成的 SQL 原文 回显给调用方。这正是 #4437 为 measure 修好的那个 ADR-0112 违背("a driver error class should never be the error.code for a caller-shaped mistake"),但 #4437 的修复只覆盖了 measure,dimension 侧的对称缺口从未被堵 。
对照组(同一类错误,measure 侧)已经被 #4437 修成 400 INVALID_FIELD 并指名字段 —— 所以这是一个纯粹的不对称 :同样是"查询里点名了对象没有的字段",measure 得到干净的 400,dimension 掉进 500。
复现(hotcrm@0899b4f + @objectstack 17.0.0-rc.2,SQLite file 驱动)
TOKEN=... # admin@objectos.ai
# ① 裸 cube 路 /analytics/query —— 不存在的 DIMENSION
curl -s -XPOST localhost:4095/api/v1/analytics/query -H " Authorization: Bearer $TOKEN " \
-H ' Content-Type: application/json' \
-d ' {"cube":"account_metrics","measures":["account_count"],"dimensions":["bogus_dim"]}'
# → 500 {"success":false,"error":{"code":"SQLITE_ERROR","message":"Internal server error","httpStatus":500}}
# ② 对照组:同一条路,不存在的 MEASURE(#4437 已修)
curl ... -d ' {"cube":"account_metrics","measures":["bogus_measure"]}'
# → 400 {"success":false,"error":{"code":"INVALID_FIELD","message":"Measure 'bogus_measure' ... which object 'crm_account' does not have. Valid measures: account_count, annual_revenue_sum. ..."}}
# ③ dataset 路 /analytics/dataset/query —— 不存在的 DIMENSION(HotCRM 所有 dashboard/report 走的就是这条路)
curl ... -d ' {"datasetName":"account_metrics","selection":{"measures":["account_count"],"dimensions":["bogus_dim"]}}'
# → 500 {"code":"ANALYTICS_QUERY_FAILED",
# "error":"SELECT bogus_dim AS \"bogus_dim\", COUNT(*) AS \"account_count\" FROM \"crm_account\" GROUP BY bogus_dim - no such column: bogus_dim"}
# 注意:整条生成的 SQL(表名/列名)被回显给调用方。
两次复现一致。①②③ 均稳定。
期望 vs 实际
落点分析(读源码 /home/user/objectstack @ cd2efe6,已领先 rc.2)
查重
analytics: a measure naming a missing field 500s with SQLITE_ERROR instead of a 400 naming the field #4437 (closed,measure 侧同题):其修复、测试(measure-source-field-gate.test.ts)、交接评论均只覆盖 measure ,dimension 从未在其 scope 内。本单是它遗留的对称缺口,引用旧单。
analytics 的 filter 拒收到不了调用方:service 侧多数拒收没有 ADR-0112 信封,REST 面又用 message 正则嗅探,一律答 500 #5352 / PR fix(analytics,rest): analytics 的 filter 拒收带上 ADR-0112 信封,REST 面先读信封 —— 400 INVALID_FILTER 而不是 500 (#5352) #5366 (REST dataset 路由先读 ADR-0112 信封 + filter 拒收带 INVALID_FILTER/400):不覆盖本单 —— 它修的是"生产方已带信封但 REST 丢弃"的通路;dimension 侧的驱动 no such column 根本不带信封,补了信封读取也没用,得先给 dimension 加闸门。
analytics dataset 路由的 message 正则兜底没有退休时间表:六族拒收仍靠措辞分类,改一个字就换一个 HTTP 码 #5367 (open,message 正则兜底的六族脆弱性):不覆盖本单 —— 那六族都是能识别措辞的 throw new Error(...);dimension 的裸 no such column 不在其列。
严重度
建议 p2 。无数字影响(所有 dataset/dashboard/report 的聚合数字在 rc.2 上对账全部正确);HotCRM 出厂 metadata 由 pnpm validate(ADR-0021)静态兜住,生产 dashboard 运行时不会撞上。真正会撞的是 Studio 预览、手写查询、外部 API 调用方 —— 他们拿到的是 500 + 泄漏的 SQL,而不是一句指名 dimension 的 400。
环境行:hotcrm@0899b4f + @objectstack 17.0.0-rc.2;落点源码 @objectstack cd2efe6(领先 rc.2,缺口在该 commit 仍在)。
现象
analytics 查询里,一个引用了不存在字段的 dimension 会一路走到 SQL,被驱动以
no such column打回,调用方拿到 500 —— 裸 driver 错误码上线,dataset 面还把生成的 SQL 原文回显给调用方。这正是 #4437 为 measure 修好的那个 ADR-0112 违背("a driver error class should never be theerror.codefor a caller-shaped mistake"),但 #4437 的修复只覆盖了 measure,dimension 侧的对称缺口从未被堵。对照组(同一类错误,measure 侧)已经被 #4437 修成
400 INVALID_FIELD并指名字段 —— 所以这是一个纯粹的不对称:同样是"查询里点名了对象没有的字段",measure 得到干净的 400,dimension 掉进 500。复现(hotcrm@0899b4f + @objectstack 17.0.0-rc.2,SQLite file 驱动)
两次复现一致。①②③ 均稳定。
期望 vs 实际
400 INVALID_FIELD+field/object/param,并列出可用 dimension)—— 就是 analytics: a measure naming a missing field 500s with SQLITE_ERROR instead of a 400 naming the field #4437 给 measure 定的那个形状。no such column以 500 上线;dataset 面还额外泄漏生成的 SQL 原文。落点分析(读源码
/home/user/objectstack@cd2efe6,已领先 rc.2)packages/services/service-analytics/src/analytics-service.ts的ensureCube()(L982)在三处调用assertMeasureFields(L999 / L1039 / L1047)校验 measure 源字段 —— 这是 analytics: a measure naming a missing field 500s with SQLITE_ERROR instead of a 400 naming the field #4437 的修复。但整个ensureCube没有任何assertDimensionFields一类的对query.dimensions的校验:dimension 名直接进入 strategy →GROUP BY <name>→ 驱动no such column。assertMeasureFields(L1079,即 analytics: a measure naming a missing field 500s with SQLITE_ERROR instead of a 400 naming the field #4437 的闸门)会err.code='INVALID_FIELD'; err.status=400,所以 measure 侧能被正确分类;dimension 侧走的是驱动抛出的SQLITE_ERROR,既无 4xxstatus也不落在 dataset REST 路由的 message 正则名单里,于是落到500 ANALYTICS_QUERY_FAILED兜底(packages/rest/src/rest-server.tsL6467-6491 的信封分支只对已带 4xx status 的错误生效;②message 正则名单不含no such column)。dimensions:["phone"]→ 200,按 phone 分组),与 measure 侧number_of_employees→SUM的自动推断对称 —— 所以缺陷只在字段不存在这一支的错误处置,与 analytics: a measure naming a missing field 500s with SQLITE_ERROR instead of a 400 naming the field #4437 描述的 measure 缺陷同形。查重
measure-source-field-gate.test.ts)、交接评论均只覆盖 measure,dimension 从未在其 scope 内。本单是它遗留的对称缺口,引用旧单。INVALID_FILTER/400):不覆盖本单 —— 它修的是"生产方已带信封但 REST 丢弃"的通路;dimension 侧的驱动no such column根本不带信封,补了信封读取也没用,得先给 dimension 加闸门。throw new Error(...);dimension 的裸no such column不在其列。严重度
建议 p2。无数字影响(所有 dataset/dashboard/report 的聚合数字在 rc.2 上对账全部正确);HotCRM 出厂 metadata 由
pnpm validate(ADR-0021)静态兜住,生产 dashboard 运行时不会撞上。真正会撞的是 Studio 预览、手写查询、外部 API 调用方 —— 他们拿到的是 500 + 泄漏的 SQL,而不是一句指名 dimension 的 400。环境行:
hotcrm@0899b4f + @objectstack 17.0.0-rc.2;落点源码@objectstack cd2efe6(领先 rc.2,缺口在该 commit 仍在)。