Skip to content

feat(spec): 声明 FilterArray 为仅输入的授权糖(#5158 拍板 C 第 1 步) - #5306

Merged
os-zhuang merged 4 commits into
mainfrom
claude/issue-5285-filterarray-input-only
Aug 4, 2026
Merged

feat(spec): 声明 FilterArray 为仅输入的授权糖(#5158 拍板 C 第 1 步)#5306
os-zhuang merged 4 commits into
mainfrom
claude/issue-5285-filterarray-input-only

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #5285
Part of #5158(维护者 2026-08-04 15:22Z 拍板 C:单一下沉缝,第 1 步)。

⛔ 本 PR 不动 engine(engine.ts Door 2)、不动四驱动、不退役 engine-findone-contract pin —— 那是第 2 步,engine 车道在本单落地后接手。

前提复核(先做的事)

按仓规先对 origin/main(88b9b2d)核实 issue 的两条前提,都成立:

  1. FilterArray 确为「有名无定义」。 全仓 grep 只在 packages/spec/src/ui/react-blocks.ts:217,244 命中,而且是字符串字面量(type: 'FilterArray'),不是任何 TS 类型或 Zod schema。对照countertest:邻居 FilterConditiondata/filter.zod.ts:219/246 有真实定义并出现在 api-surface.jsonmalformedFilterArrayError(protocol.ts)只是同名前缀的函数,不是声明。
  2. parseFilterAST 是下沉函数。 真实导出路径 packages/spec/src/data/filter.zod.ts:646,经 data/index.tsexport * from './filter.zod' 出现在 @objectstack/spec/data。协议门(metadata-protocol/src/protocol.ts:4534)正是 isFilterAST() 通过则 parseFilterAST(),否则 400 INVALID_FILTER

落点判断(以读码为准)

放进 packages/spec/src/data/filter.zod.ts,紧接 parseFilterAST 之后。理由:算子词表(AST_OPERATOR_MAP / VALID_AST_OPERATORS)、结构判定(isFilterAST)、下沉函数(parseFilterAST)全在这一个文件里;声明必须由它们派生才不会漂移,放到别处只能靠复述。query.zod.tsBaseQuerySchema.where 的家,而本形状恰恰必须不出现在那里,所以它不是落点。

形状:三个独立权威互相印证,无歧义

不是凭记忆写的。三处独立实现对结构的读法完全一致:

权威 比较节点 逻辑组 扁平列表
isFilterAST(运行时判定) [field, op, value],op ∈ VALID_AST_OPERATORS ['and'|'or', ...children] [[cond], [cond]]
FilterBuilder(packages/client/src/query-builder.ts:18,实测 16 个方法) 同上,push([field, '=', value]) ['and', ...conditions] getConditions() 返回的数组
packages/lint/src/validate-react-page-props.ts:607 filterFieldRefs 同上(且要求 field 非空) 同上 同上

据此声明:

  • FilterArrayComparison —— 含真实存在的二元形式:空值判定的方向在算子名里(['deleted_at', 'is_null']),convertComparison 对这类根本不读 value 位,isFilterAST 只要求 length >= 2
  • FilterArrayGroup —— 至少一个子条件(['and'] join 不到任何东西,A filter array that isn't a valid AST reaches the driver as an opaque where — reject it at the protocol instead #4121 已专门堵过)。
  • FilterArrayList —— 非空;[] 意思是「没有过滤」,是本形状的缺席而非其实例。
  • FilterArrayOperator —— keyof typeof AST_OPERATOR_MAP,派生而非复述。为此把 AST_OPERATOR_MAPRecord< string, string > 注解换成 satisfies(保住字面量键集),两处运行时按 string 取值统一走新的 astOperatorLowering()。行为完全不变,全部驱动/协议测试为证。

一处由实测定调的细节:算子必须大小写不敏感VIEW_FILTER_OPERATOR_ALIASES(ui/view.zod.ts:197)明确记载已存储的 view 元数据携带 startsWith / notEquals / greaterThan 这类 camelCase 拼写,而每扇门都先 toLowerCase() 再查表。若这里用 z.enum 做大小写敏感校验,就会拒掉 wire 今天正常接受的过滤器。故 schema 用与 isFilterAST 逐字节相同的谓词;TS 类型则只列规范拼写(与隔壁 ViewFilterOperator 只列规范、别名走 *_ALIASES 的既有分工一致,不是新发明)。

仅输入 + 负向 pin

FilterArraySchema授权门,isFilterAST 仍是运行时判定门。两者共用一套词表和一套大小写折叠,只在 2 处刻意更严(都是 isFilterAST 顺带容忍、没有任何生产者产出、且明显是作者笔误的形状):尾部多余元素 ['a','=',1,2](convertComparison 会静默丢弃)、空字段名 ['','=',1]。这个差异清单在测试里逐条钉住,不能悄悄变长。

负向 pin(本 PR 的承重件):filter-array-declaration.test.ts 断言 QuerySchema.safeParse({ object, where: <任一 FilterArray> }) 必须失败,并对 FilterConditionSchema 再钉一层(防止绕过 where 直接放宽条件类型)。将来有人「顺手」把数组方言扩进协议面,这条立刻变红并指回 #5158 的裁决。同时反向钉住:同一个值经 parseFilterAST 下沉后 where 接受 —— 说明它不是「过滤器写错了」,而是「还没下沉」。

验证

  • pnpm --filter @objectstack/spec test309 files / 7957 tests passed(含新增 13 条)
  • pnpm --filter @objectstack/spec typecheck → 通过
  • check:generated9/9 up to date(api-surface.json 新增 7 个导出、json-schema.manifest.json 新增 data/FilterArraycontent/docs/references/data/filter.mdx.describe() 重生成,全部由 --fix 窄重生成)
  • check:exported-any → 通过(1850 types / 1596 schemas,FilterArray 未落成 any)
  • 消费半径清扫(改动了 canonicalAstOperator / convertComparison 的查表):driver-sql 729 passed、driver-memory 286 passed、metadata-protocol 376 passed、lint 1198 passed、objectql 1868 passed、client 222 passed

反向验证 —— 方向与模板预设不同,如实记录

预测:把 satisfies 改回 Record< string, string >(算子类型放宽回 string)后,类型级断言应转红。首次运行却全绿,预测失败,而失败原因比预测本身更重要:

packages/spec/tsconfig.jsonexclude 含测试通配(TEST_DEBT 有实测条目:272 文件 / 902 错误),所以 pnpm typecheck 从不读取任何测试文件 —— 本 PR 里的两处 @ts-expect-error 在 CI 中是惰性的,即幻影断言。

解除排除后重做,方向即与预测一致:基线 exit 0(两处断言都是活的);放宽算子类型后有且仅有 src/data/filter-array-declaration.test.ts(215,5): error TS2578: Unused '@ts-expect-error' directive. —— 正是本声明新增的收窄。

处理方式:不伪造证据。类型级断言保留(它们是对的,spec 从 TEST_DEBT 毕业当天即生效),但在测试文件里显式标注「CI 不做类型检查」、附上手工复现命令与结果,并把这一类问题(spec 测试层共 17 处 @ts-expect-error 全部惰性)按 Prime Directive #10 另立 #5305(观察类,finding,未认领)。请勿把 pnpm test 全绿读作该块已被验证。

changeset

.changeset/filter-array-input-only-declaration.md,spec minor(新增导出类型)。无迁移:此前能用的过滤器一律照旧,只是补上了原本缺失的那道校验。

🤖 Generated with Claude Code

https://claude.ai/code/session_01ErbEDVAg1No9gdg1pgDAGB


Generated by Claude Code

`FilterArray` was a name with no definition. Three READMEs, `llms.txt`, four
skills, the query-adapter docs and this package's own react-blocks prop table
all taught authors to write it, while the protocol never declared it anywhere.

Declare it in `data/filter.zod.ts`, beside the operator vocabulary it is built
from and the sink it lowers through:

- `FilterArray` + `FilterArrayComparison` / `FilterArrayGroup` /
  `FilterArrayList` — the three shapes the measured producers emit
- `FilterArraySchema` — the Zod authoring gate
- `FilterArrayOperator` — canonical spellings, derived from `AST_OPERATOR_MAP`
  rather than restated, so it cannot drift from the lowering (#3948)
- `FILTER_ARRAY_LOGIC_KEYWORDS` / `FilterArrayLogicKeyword`

Input-only: lowered to a `FilterCondition` at the single sink `parseFilterAST`
on arrival. The storage/wire contract is unchanged — a query's `where` is a
`FilterCondition` and deliberately does NOT accept the array dialect, pinned as
a negative test so a future widening of the protocol face fails loudly.

`AST_OPERATOR_MAP` keeps its literal key set via `satisfies` so the operator
type can be derived; the two runtime lookups go through one `astOperatorLowering`
helper. Behaviour identical.

Step 1 of #5158's ruling C. No engine or driver changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ErbEDVAg1No9gdg1pgDAGB
@vercel

vercel Bot commented Aug 4, 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 4, 2026 8:40pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation protocol:data tests tooling size/l labels Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

107 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/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/spec)
  • content/docs/api/index.mdx (via @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/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/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • 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/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/spec)
  • content/docs/plugins/packages.mdx (via @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/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @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/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @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/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.

claude added 2 commits August 4, 2026 20:30
)

Serial-landing sync for PR #5306. `git merge origin/main`, then all NINE
os-regen paths reset to origin/main and regenerated wholesale from the merged
sources (the path list read from the merged tree's `.gitattributes`, not from
memory — #5304 added `authorable-surface.base.json` as the ninth).

The merge driver had deferred `api-surface.json` and `json-schema.manifest.json`,
and git's textual result RESURRECTED symbols three sibling PRs had retired —
this branch's pre-merge copies still listed them. Wholesale regeneration removes
them again:

- #5293: HttpServerConfig / HttpServerConfigInput / HttpServerConfigSchema
- #5289: Animation / AnimationSchema / ZIndex / ZIndexSchema
- #5300: EmbedConfig / EmbedConfigSchema / NotificationAction /
  NotificationActionSchema

Verified after regeneration: this PR's 7 exports and `data/FilterArray` still
present, the FilterArray docs section still carries its describe text, all three
siblings' retirements absent from every witness, the #5304 anchor authentic
(baseRev an ancestor of origin/main, keys identical line-for-line to that
commit's surface), and `check:merge-driver` reconciling 9 paths both ways.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ErbEDVAg1No9gdg1pgDAGB

Copy link
Copy Markdown
Contributor Author

串行落地同步完成(base 已推进到 04fab5e,head d1ab61e)

按协议执行,第 3 步的新变量已按合并后树核对,未用记忆里的 8 条清单

os-regen 路径:实测 9 条

从合并后树的 .gitattributes 逐条读出(grep -oP '^\S+(?=\s+merge=os-regen)'),而非复述:

packages/spec/spec-changes.json
packages/spec/authorable-surface.json
packages/spec/authorable-surface.base.json      ← #5304 新增的第 9 条
packages/spec/json-schema.manifest.json
packages/spec/api-surface.json
packages/spec/api-surface-signatures.json
docs/protocol-upgrade-guide.md
docs/audits/2026-07-unknown-key-strictness-ledger.counts.md
content/docs/references/**

pnpm check:merge-driver.gitattributes ↔ regen-artifacts.mjs agree on 9 path(s),双向一致。

合并驱动确实丢了一侧 —— 这次抓到了实例

merge 后 os-regen-pending 记录了 2 条延迟路径(api-surface.jsonjson-schema.manifest.json)。git 的文本结果保留了本分支合并前的旧副本,于是把三个兄弟 PR 已退役的符号复活了。整体重生成后逐一消失:

来源 被复活又被清除的符号
#5293 HttpServerConfig / HttpServerConfigInput / HttpServerConfigSchema
#5289 Animation / AnimationSchema / ZIndex / ZIndexSchema
#5300 EmbedConfig / EmbedConfigSchema / NotificationAction / NotificationActionSchema

这正是「窄修会静默出错、必须整体重生成」的活标本。

断言结果(全通过)

a. 本 PR 自己的条目仍在 —— api-surface 8 个新导出全部 PRESENT;json-schema.manifest.jsondata/FilterArray PRESENT;content/docs/references/data/filter.mdx 重生成后仍含 ## FilterArray 段、Input-only authoring sugar for a filter describe 正文与 parseFilterAST 下沉指向。负向 pin 13/13 绿。

b. 兄弟条目在场 —— ui/Theme:animation [RETIRED] / ui/Theme:zIndex [RETIRED] 在 authorable-surface 中在场;conversions 注册表 theme-inert-token-scales-removed 在场(九个 token 面);migrations 注册表 D3 条目 ui-notification-action-embed-config-retired 在场;#5293 / #5300 的符号在 authorable-surface / api-surface / json-schema.manifest 三个 witness 同时缺席(各 0 命中)。

c. #5304 锚点 —— ⚠️ 有一处与派单措辞的实测偏差,如实报告,未强行凑合:

派单要求 baseRev == git merge-base HEAD origin/main。实测 baseRev = 26e1029,而 merge-base = 04fab5e,不相等。核对生成器后确认这是设计如此,不是失败:

  • build-schemas.ts:1208 只在 keys 漂移时重写锚点(drifted = !committed || keys 不等),baseRev 不同不触发重写;
  • regen-artifacts.mjs:26-33build-schemas.ts:1201-1203 都明写:「a stale copy of this one is NOT an error……check:authorable-surface proves it AUTHENTIC rather than current」;
  • 真正的闸门 verifyCommittedSurfaceBase 查两件事,均已单独验证通过:baseRev 是 origin/main 的祖先(git merge-base --is-ancestor 通过,且 26e102904fab5e 的父提交,属上游而非本分支提交)、② 记录的 keys 与该 commit 的 authorable-surface.json 逐行一致(8016 == 8016,JSON.stringify 全等)。

按等号去改这个文件需要手编锚点,而生成器把手编此文件称作「the #4650 attack itself」,故未改。check:authorable-surface 自身已绿(输出明确带 (+ its .base.json anchor))。若维护者确实要让 baseRev 前进,正确做法是等 keys 发生漂移时由 gen:schema 自动推进,而不是本 PR 手写。

验证(合并后树)

  • pnpm --filter @objectstack/spec typecheck → 通过
  • pnpm --filter @objectstack/spec test310 files / 7970 tests passed(较同步前 309/7957 增长的部分是 main 自带的新测试,说明测的是合并态)
  • pnpm --filter @objectstack/spec check:generated(verify 模式)→ All 9 generated artifacts are up to date
  • filter-array-declaration.test.ts 单独重跑 → 13/13 绿,含负向 pin a query 'where' does NOT accept the array dialect
  • check:nul-bytes OK + 对改动文件的控制字节自查(grep exit 1,干净)
  • pre-commit 钩子确认两条延迟产物 current 并清除 marker

最终 delta vs origin/main 纯增量:api-surface +8 / -0,manifest +1 / -0,未扰动任何兄弟条目。

普通 push,未 force,未 rebase。


Generated by Claude Code


Generated by Claude Code

@os-zhuang
os-zhuang enabled auto-merge August 4, 2026 20:42
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 4, 2026
Merged via the queue into main with commit b49ccfd Aug 4, 2026
25 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-5285-filterarray-input-only branch August 4, 2026 21:03
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/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[spec] 声明 FilterArray 为仅输入的授权糖(#5158 拍板 C 第 1 步,拆单移交 spec 车道)

2 participants