Skip to content

fix(data): searchFields / groupBy / aggregations 指向不存在的字段时被拒绝,而不是静默降级 (#4254) - #4315

Merged
os-zhuang merged 4 commits into
mainfrom
claude/rest-read-path-field-degradation-4240d5
Jul 31, 2026
Merged

fix(data): searchFields / groupBy / aggregations 指向不存在的字段时被拒绝,而不是静默降级 (#4254)#4315
os-zhuang merged 4 commits into
mainfrom
claude/rest-read-path-field-degradation-4240d5

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes #4254

TL;DR

#4226(PR #4240)把 sort / select / expand 收口后,同一台机器在剩下三条点名字段的轴上继续漏气。三条现在都在共享的 normalizerfindData)里被拒绝,于是 GET /data/:objectPOST /data/:object/query、export 路由和 runtime dispatcher 给出同一个答案:

search=alpha&searchFields=no_such -> 之前 200 + 比收窄更多的行     现在 400 INVALID_FIELD
groupBy=[no_such]                 -> 之前 200 + N 组塌成 1 组       现在 400 INVALID_FIELD
sum(no_such)                      -> 之前 200 + 求和得 0            现在 400 INVALID_FIELD

三条轴分别怎么改的

searchFields400 INVALID_FIELD,三段式消息

issue 点名要与 #4226expand 的三段式同构,落地为三条消息,因为修法不同:

  1. 根本不是字段 —— 请求里的拼写错误。带 Did you mean 建议;点号路径(parent_id.title)单独提示「search 只扫本对象自己的列」,因为引擎按精确名求交集,头段校验会把回退兜回来。
  2. 是字段但不可搜索 —— 修的是对象。消息按 allowed 集的来源分两支:声明了 searchableFields 的对象指向声明本身;auto-default 的对象说明该字段被排除的原因(类型 / 系统列 / hidden)并给出「声明 searchableFields」的出路。
  3. searchableFields 里声明了、字段却不存在 —— 陈旧声明,bug 在对象上。单独一条消息是因为 objectui 的列表搜索把 schema.searchableFields 原样回声$searchFieldsListView.tsx),把它报成「调用方拼错」会让人去修根本没写错的请求。仍然是 400:全部请求名都陈旧时,引擎的回退会扫默认全集 —— 正是本轴要堵的「要收窄、结果放宽」。

两支(全未知 / 部分未知)都拒,与 issue 的裁决一致。allowed 集由 resolveSearchFieldResolution 解析 —— 该函数从 objectql 移入 @objectstack/spec/datasearch-fields.ts),引擎的 search 展开和这个 gate 消费同一份实现,gate 不可能放行一个引擎会丢弃的名字,也不可能拒掉一个引擎会扫的(#4240REFERENCE_VALUE_TYPES 堵 expand 漂移的同一手法)。引擎侧 resolveSearchFields 的容忍未动(内部调用方不经过 ingress)。

覆盖引擎实际读取的每种拼写searchFields / $searchFields(逗号串与数组)以及 search: { query, fields } 对象形态(数组与逗号串——第二种是复查引擎时发现的:engine.findrequestedFields 也消费字符串形态,只按 Array.isArray 镜像会留下缺口)。报错引用调用方真正写下的参数名。

groupBy400 INVALID_FIELD

in-memory 回退路径把未知列对每行投影成 undefined ?? null,所有行进同一个桶:[{no_such: null, n: 3}] —— n 是真实行数,结构完全合法,图表照画一根柱子。SQL 原生路径则把 GROUP BY no_such 交给数据库(SqlDriver 是否吞错未定)——issue §4 点名的「两条路径可能给出相反答案」,在共享 ingress 收口后两条路径先给出同一个 400。字符串与结构化 {field, dateGranularity} 两种形态都校验;按精确名判定(分组语义就是本对象的列)。

aggregations400 INVALID_FIELD

sum(<拼错>) 把一列 undefined 折成 0,和真实的「本季度 0」同形;avg/min/maxnull 同理。aggregations[].field 逐项校验;countfield(或 '*' 哨兵)是唯一合法的无字段形态,放行。

顺带:无法读取的形态 → 400 INVALID_QUERY(catalog 里首个 emitter)

groupBy: "status"(裸字符串)、[42]{dateGranularity:'month'}(没有 field)、枚举外的 function / dateGranularity、缺 alias —— 每一种此前要么被 Array.isArray 路由守卫忽略(行未分组原样返回),要么算出静默占位值(null 结果、键名 "undefined" 的列、未知粒度下一行一桶)。INVALID_QUERY("Malformed query syntax")自写进标准 catalog 起没有任何 emitter —— 与 #4240 启用 INVALID_SORT 同一姿势。形态检查不依赖 registry(分层里 legacy/registry-less 宿主也拒形态,只跳过字段名检查),与 #4196 投影形态检查同序。

分层与边界

测试

packages/objectql/src/query-expression-conformance.test.ts 新增 #4254 describe 块(37 个用例,全文件 77 个全绿),照搬 #4240 的纪律:

  • 每条轴都有对照组,且这次对照组要求测试驱动真的执行:共享 memory driver 补了 $or / $contains 求值(否则任何 search 都全命中,「searchFields 真的收窄了行集」的断言 vacuously 绿),聚合用例删掉 driver 的 aggregate stub 走引擎真正的 in-memory 回退(issue 实测的那条路径)。
  • issue transcript 逐条落地:search=a 命中 title 与 notes 各一行 → searchFields=title 收到一行 → searchFields=no_such 400(此前是两行);groupBy=[status] 真分两组 → [no_such] 400(此前一桶);sum(estimate) 真合计 → sum(no_such) 400(此前 0)。
  • 另有:五种 override 拼写同答案并引用原参数名、声明式与 auto-default 两支的「不可搜索」消息、陈旧声明(objectui 回声形)、日期分桶对照与非法粒度、count(*) 两种拼写放行、七轴合成请求、404 优先、legacy 数组字段表只降级字段名检查。
  • 全仓 pnpm test132/132 任务绿(含 dogfood HTTP 级 430 例)。check:generated(api-surface 已重生成)、check:livenesscheck:exported-any 均绿。

文档

data-api.mdx#4240 的「Nor is a sort…」旁新增三轴一节(请求 → 结果对照表 + 各轴为何要紧);参数表补 search / searchFields 行;error-catalog.mdxINVALID_QUERY 从占位描述改为写明其 emitter,INVALID_FIELD 列全七条轴;queries.mdx / query-syntax.mdx 在聚合与搜索小节各加 ingress 行为 callout。search 会格 ledger(search-conformance.ledger.ts)的 enforcement 指针随实现迁移更新。

对调用方的影响

  • 点名真实字段的请求不受影响。
  • searchFields / groupBy / aggregations[].field 中点名不存在字段的请求现在显式失败,而不是收到一个被放宽 / 未分组 / 合计为 0 的 200。
  • 一个已知的边缘:对象的 searchableFields 声明里若有陈旧条目(字段后来改名),objectui 回声该声明的列表搜索会开始收到 400(消息直指对象与修法)。配套的 authoring-time lint(searchableFields ⊆ fields)已作为后续任务另开。

关联


Generated with Claude Code

os-zhuang and others added 2 commits July 31, 2026 13:24
…ld are rejected, not silently degraded (#4254)

#4226 closed sort / select / expand; the same machine kept leaking on the
remaining three field-naming read axes, and each failure corrupted something
the closed axes never touched:

    search=alpha&searchFields=no_such -> 200  MORE rows than the narrowing allowed
    groupBy=[no_such]                 -> 200  [{no_such: null, n: <true count>}]
    sum(no_such)                      -> 200  0 - indistinguishable from a real zero

Each is now refused at the shared normalizer (findData), so the list route,
POST /data/:object/query, the export route and the runtime dispatcher give one
answer instead of four.

- searchFields -> 400 INVALID_FIELD. The select failure with the sign flipped
  outward: dropped unknown names emptied the override, which fell back to the
  FULL searchable set - a narrowing parameter that widened, changing which
  ROWS came back. Three messages (typo / real-but-unsearchable / stale
  searchableFields declaration), because the fixes differ. The allowed set is
  resolved by the same spec/data function the engine's search expansion
  consumes (resolveSearchFieldResolution, moved from objectql), so gate and
  engine cannot drift.
- groupBy -> 400 INVALID_FIELD. The in-memory fallback projected an unknown
  column as null for every row: N groups collapsed into one null-keyed bucket
  carrying the true row count.
- aggregations -> 400 INVALID_FIELD. sum(<typo>) folded undefined to 0;
  avg/min/max answered null. count with no field (or '*') stays legal.
- Unreadable SHAPES on the aggregation axes -> 400 INVALID_QUERY - the
  catalog code that had no emitter, like INVALID_SORT before #4226.

Tiering mirrors #4226 (no registry / no field map / legacy array map -> name
gates skip; shape gates still apply). Engine tolerance for internal callers is
untouched. @objectstack/rest stops logging INVALID_FILTER / INVALID_SORT /
INVALID_QUERY rejections as unhandled errors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 31, 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 Jul 31, 2026 6:58am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation protocol:data tests tooling size/xl labels Jul 31, 2026
@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 5 package(s): @objectstack/metadata-protocol, @objectstack/objectql, @objectstack/dogfood, @objectstack/rest, @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/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/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 @objectstack/metadata-protocol, @objectstack/objectql, packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/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 packages/objectql, @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/migration-from-objectql.mdx (via @objectstack/objectql)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/deployment/vercel.mdx (via @objectstack/objectql)
  • 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/metadata-protocol, @objectstack/objectql, @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/permissions/authentication.mdx (via @objectstack/objectql, @objectstack/rest)
  • content/docs/permissions/authorization.mdx (via packages/qa/dogfood, @objectstack/spec)
  • content/docs/permissions/delegated-administration.mdx (via packages/qa/dogfood)
  • 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/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/objectql, @objectstack/rest, @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/objectql, @objectstack/rest, @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/i18n-standard.mdx (via packages/rest, @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.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 packages/objectql, @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/objectql, @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/objectql, @objectstack/rest, @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/spec)
  • content/docs/releases/v9.mdx (via @objectstack/metadata-protocol, @objectstack/spec)
  • content/docs/ui/actions.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.

…-field-degradation-4240d5

# Conflicts:
#	packages/metadata-protocol/src/protocol.ts
@os-zhuang
os-zhuang merged commit af2a095 into main Jul 31, 2026
25 of 27 checks passed
@os-zhuang
os-zhuang deleted the claude/rest-read-path-field-degradation-4240d5 branch July 31, 2026 07:35
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 protocol:data size/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

REST 读路径:searchFields / groupBy / aggregations 指向不存在的字段时被静默降级(#4226 收口后剩下的三条轴)

1 participant