You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
analytics: a measure naming a missing field 500s with SQLITE_ERROR instead of a 400 naming the field #4437
Found while verifying the 17.0.0-rc.1 checklist on #3909 (F5). Verified on main @ 1ee48bc60, showcase under os serve --dev.
The declared-aggregation half of the analytics honesty axis works (total_sum emits SUM(total), #4184/#4153 hold), and the DATA route already refuses a missing field on searchFields/groupBy/aggregations with a 400 naming the field (#4315/#4254). But the ANALYTICS route lets a measure naming a nonexistent field fall through to SQL:
POST /api/v1/analytics/query {"cube":"showcase_invoice","measures":["ghost_sum"]}
→ 500 {"success":false,"error":{"code":"SQLITE_ERROR","message":"Internal server error","httpStatus":500}}
inferMeasure('ghost_sum') happily builds SUM(ghost), the driver throws no such column, and the caller gets an opaque SQLITE_ERROR 500 — a driver name on the wire and nothing actionable. A dotted spelling takes the same path ("measures":["total.sum"] → prefix-strip → inferMeasure('sum') → 500 SQLITE_ERROR).
Expected: measure resolution validates the inferred source field against the cube/object schema BEFORE building SQL, and refuses with a 400 naming the field and the valid measures — the same shape the data route's #4315 refusal already has (and the dateRange-family validation on this same endpoint already achieves for its own inputs). A driver error class should never be the error.code for a caller-shaped mistake (ADR-0112).
Activity
baozhoutao commentedon Aug 1, 2026
Tracked under the v17 verification tracker #3909 (section F, rc.1 run 1).
baozhoutao commentedon Aug 1, 2026
Rolled up in #4482 (all 17 defects from the v17 verification, grouped by severity with a suggested RC-exit triage).
os-zhuang commentedon Aug 1, 2026
交接说明(进行中)
分支:
claude/v17-verification-defects-gnf9e6-analytics(framework 仓库,已推送)已完成
--fresh):4372aef)。AnalyticsService.ensureCube现在在任何 SQL 被构造之前,把每个 measure 解析出来的源字段拿去和承载对象的字段名核对,拒绝时用与 data route 完全相同的信封(400 INVALID_FIELD+field/object/param,fix(data):searchFields/groupBy/aggregations指向不存在的字段时被拒绝,而不是静默降级 (#4254) #4315/REST 读路径:searchFields/groupBy/aggregations指向不存在的字段时被静默降级(#4226 收口后剩下的三条轴) #4254 的那一套):门槛沿用 #3867 那个推断闸门的分级:只在 cube 的
sql是裸对象名时检查、只在新增的getObjectFieldNames探针能回答时检查、只检查源是裸列名的 measure(count(*)和account.industry这类跨对象点号引用一律放行)。校验发生在 cube 注册之前,所以被拒绝的查询不会在 registry 里留下痕迹(否则重试会撞上"已注册"的脏 cube 直接冲进 SQL)。当前卡在哪
不算卡住。有一个待打磨的小瑕疵:错误信息里的 "Valid measures:" 在自动推断 cube 这条路上会把出错的那个 measure 自己也列进去(实测输出
Valid measures: count, ghost_sum),因为inferCubeFromQuery已经把它塞进cube.measures了。不影响状态码和field字段的正确性,但读起来会误导。下一步具体该做什么
packages/services/service-analytics/src/__tests__/cube-inference-gate.test.ts的写法新开一个文件:覆盖ghost_sum、点号total.sum、count正常放行、探针缺失时 stand down、以及"被拒绝不污染 registry"。pnpm --filter @objectstack/service-analytics test、pnpm typecheck。Generated by Claude Code
test: pin the analytics scoping + measure-field gates (#4467, #4437)
chore: add changeset for the analytics scoping + measure-field fixes (#…
fix(analytics): 记录级权限范围与 measure 字段校验 (objectstack-ai#4467, objectsta…
fix(service-analytics,rest): gate analytics DIMENSIONS on the object'…
fix(service-analytics): gate `where` source fields — a filter over a …