feat(spec): 声明 FilterArray 为仅输入的授权糖(#5158 拍板 C 第 1 步) - #5306
Conversation
`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
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
…terarray-input-only
📓 Docs Drift CheckThis PR changes 1 package(s): 107 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…terarray-input-only
) 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
串行落地同步完成(base 已推进到
|
| 来源 | 被复活又被清除的符号 |
|---|---|
| #5293 | HttpServerConfig / HttpServerConfigInput / HttpServerConfigSchema |
| #5289 | Animation / AnimationSchema / ZIndex / ZIndexSchema |
| #5300 | EmbedConfig / EmbedConfigSchema / NotificationAction / NotificationActionSchema |
这正是「窄修会静默出错、必须整体重生成」的活标本。
断言结果(全通过)
a. 本 PR 自己的条目仍在 —— api-surface 8 个新导出全部 PRESENT;json-schema.manifest.json 的 data/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-33与build-schemas.ts:1201-1203都明写:「a stale copy of this one is NOT an error……check:authorable-surfaceproves it AUTHENTIC rather than current」;- 真正的闸门
verifyCommittedSurfaceBase查两件事,均已单独验证通过:①baseRev是 origin/main 的祖先(git merge-base --is-ancestor通过,且26e1029是04fab5e的父提交,属上游而非本分支提交)、② 记录的 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 test→ 310 files / 7970 tests passed(较同步前 309/7957 增长的部分是 main 自带的新测试,说明测的是合并态)pnpm --filter @objectstack/spec check:generated(verify 模式)→ All 9 generated artifacts are up to datefilter-array-declaration.test.ts单独重跑 → 13/13 绿,含负向 pina query 'where' does NOT accept the array dialectcheck:nul-bytesOK + 对改动文件的控制字节自查(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
Fixes #5285
Part of #5158(维护者 2026-08-04 15:22Z 拍板 C:单一下沉缝,第 1 步)。
⛔ 本 PR 不动 engine(
engine.tsDoor 2)、不动四驱动、不退役engine-findone-contractpin —— 那是第 2 步,engine 车道在本单落地后接手。前提复核(先做的事)
按仓规先对
origin/main(88b9b2d)核实 issue 的两条前提,都成立:FilterArray确为「有名无定义」。 全仓grep只在packages/spec/src/ui/react-blocks.ts:217,244命中,而且是字符串字面量(type: 'FilterArray'),不是任何 TS 类型或 Zod schema。对照countertest:邻居FilterCondition在data/filter.zod.ts:219/246有真实定义并出现在api-surface.json。malformedFilterArrayError(protocol.ts)只是同名前缀的函数,不是声明。parseFilterAST是下沉函数。 真实导出路径packages/spec/src/data/filter.zod.ts:646,经data/index.ts的export * 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.ts是BaseQuerySchema.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:607filterFieldRefs据此声明:
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 opaquewhere— reject it at the protocol instead #4121 已专门堵过)。FilterArrayList—— 非空;[]意思是「没有过滤」,是本形状的缺席而非其实例。FilterArrayOperator——keyof typeof AST_OPERATOR_MAP,派生而非复述。为此把AST_OPERATOR_MAP的Record< 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 test→ 309 files / 7957 tests passed(含新增 13 条)pnpm --filter @objectstack/spec typecheck→ 通过check:generated→ 9/9 up to date(api-surface.json新增 7 个导出、json-schema.manifest.json新增data/FilterArray、content/docs/references/data/filter.mdx随.describe()重生成,全部由--fix窄重生成)check:exported-any→ 通过(1850 types / 1596 schemas,FilterArray未落成any)canonicalAstOperator/convertComparison的查表):driver-sql729 passed、driver-memory286 passed、metadata-protocol376 passed、lint1198 passed、objectql1868 passed、client222 passed反向验证 —— 方向与模板预设不同,如实记录
预测:把
satisfies改回Record< string, string >(算子类型放宽回string)后,类型级断言应转红。首次运行却全绿,预测失败,而失败原因比预测本身更重要:packages/spec/tsconfig.json的exclude含测试通配(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