Repository navigation
Description
发现于 #5500(flow.test.ts fixture 形状普查)第 4 类的契约测量。按 Prime Directive #10 单独记录,未在那个 PR 里顺手改。
事实
packages/spec/src/data/filter.zod.ts:92 起:
export const ComparisonOperatorSchema = lazySchema(() => z.object({
$gt: z.union([z.number(), z.date(), FieldReferenceSchema]).optional(),
$gte: z.union([z.number(), z.date(), FieldReferenceSchema]).optional(),
$lt: z.union([z.number(), z.date(), FieldReferenceSchema]).optional(),
$lte: z.union([z.number(), z.date(), FieldReferenceSchema]).optional(),
}));
即:声明里没有 string。但平台自己在这四个槽位里传的恰恰只有字符串:
-
日期宏解析器只能产出字符串。
packages/core/src/utils/filter-tokens.ts:18-19的规范示例就是这个形状:
{ close_date: { $gte: '{current_year_start}' } }→{ close_date: { $gte: '2026-01-01' } }。
实现侧:303case 'now': return now.toISOString();、:319addUnits(...).toISOString()、asYmd(...)全部返回 string,没有任何分支返回number或Date。
packages/spec/src/data/date-macros.zod.ts:34-35也明说「the DRIVER only ever sees ISO date / timestamp strings」。 -
一方调用方一律传
.toISOString():packages/objectql/src/lifecycle/lifecycle-service.ts:991-992—const keepCutoff = new Date(...).toISOString();→{ created_at: { $lt: keepCutoff } }- 同文件
:1054-1055—tCutoff同样是.toISOString() packages/plugins/plugin-email/src/outbox-sweep.ts:155,160—const cutoff = new Date(now - minAgeMs).toISOString();→{ created_at: { $lt: cutoff } }
为什么今天没人撞到
ComparisonOperatorSchema 没有任何运行期消费者。仓库内 grep 只命中它自己的单测(filter.zod 的定义、data/filter.test.ts)和导出桶,objectql 的读写路径、各 driver、flow 的 CRUD executor 都不拿它校验 where。flow 节点侧的 filter 另有声明(z.record(z.string(), z.unknown()),builtin-node-config.zod.ts:219),值一律 unknown,所以也到不了这里。
也就是说:这是一处 declared 与 practiced 不一致,但 declared 那侧不设防,所以不产生运行期失败 —— 按 finding 记录,严重度留给分诊轮。
危害面
它仍然是一份发布出去的契约:作者(尤其 AI 作者)读 $lt: number | date | FieldReference 会得出「日期窗口要传 Date 对象或时间戳数字」的结论,而平台全部通路实际产出并接受的是 ISO 字符串。#5500 的分诊注记正是卡在这个问题上(「插值后的 ISO 日期串走 record-form where 是否是我们要教的形状」)。
可选方向(未拍板)
- A. 补
string进联合(并在.describe()里点明 ISO date / date-time 字符串是规范形态)。与filter-tokens.ts的自带示例、与全部一方调用方一致,改动最小。代价:string很宽,拿不到格式校验。 - B. 补一个受约束的字符串(如 ISO date / date-time 的 refine),表达力更准,但要确认 driver 侧对 date-only 与 full-timestamp 的处理一致。
- C. 维持声明、改调用方传
Date。与日期宏解析器的产物冲突(它只能给字符串),等于要求每个消费点再把宏解析结果转回Date,和 ADR 里「driver 只看 ISO 串」的既定方向相反。
倾向 A 或 B —— C 与既有 ADR 方向相反,不宜。
Activity
claude commentedon Aug 6, 2026
发现分诊轮判级(#4949 纪律):晋级
pm:queue,域domain:spec保持。过时前提检查:origin/main
889ae47上packages/spec/src/data/filter.zod.ts的ComparisonOperatorSchema四个槽位仍是z.union([z.number(), z.date(), FieldReferenceSchema]),无string,其上方注释仍写 "Supported data types: Number, Date";前提成立。晋级理由:这条不是"declared ≠ enforced"的常见形态(声明了没人兑现),而是反向的 —— 平台自己的生产者只产出声明里没有的类型:日期宏解析器(
packages/core/src/utils/filter-tokens.ts)全部分支返回 string,packages/spec/src/data/date-macros.zod.ts明写「the DRIVER only ever sees ISO date / timestamp strings」。也就是说修法方向不需要裁决:契约面已由 date-macros 一侧写死为字符串,声明侧对齐现实即可(把z.string()并进四个槽位的联合,或引入一个日期字符串 schema),不存在"改生产者还是改声明"的分叉。z.string();② 该 schema 是公共契约面,按「共享契约面单一所有者」归domain:spec座位,须带 changeset。本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
hotlong commentedon Aug 7, 2026
Release-board audit (maintainer-directed re-audit of non-board
domain:specitems, 2026-08-07): addingtarget:v17— criterion ②: the publishedComparisonOperatorSchemaomitsstringfrom$gt/$gte/$lt/$ltecomparands while the platform's own date-macro parser and first-party callers send only ISO strings there. Declared ≠ enforced on the public filter contract. Triage seat may veto.Generated by Claude Code
fix(spec): constrain ViewFilterRuleSchema.value by operator (objectst…
feat(spec): a skill trigger condition's `value` must have the shape i…
test(spec): security, ai, identity, integration, migrations, marketpl…
test(spec): the fourth data/ file group's test titles state each cite…
test(spec): the third ui/ file group's test titles state each cited d…