Skip to content

fix(spec): $gt/$gte/$lt/$lte 接受平台自己产出的 ISO 字符串 (#5685) - #6570

Merged
qq9340100 merged 2 commits into
mainfrom
claude/issue-5685-comparison-operator-string
Aug 8, 2026
Merged

fix(spec): $gt/$gte/$lt/$lte 接受平台自己产出的 ISO 字符串 (#5685)#6570
qq9340100 merged 2 commits into
mainfrom
claude/issue-5685-comparison-operator-string

Conversation

@qq9340100

@qq9340100 qq9340100 commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

Fixes #5685

四个排序比较槽位声明为 number | Date | FieldReference,而平台自己的生产者往这四个槽位里放的恰恰只有字符串。这不是「声明得不够细」,是声明与现实相矛盾

前提复核(origin/main @ 02479cc91,逐条核对 issue 事实)

# issue 断言 复核结果
1 filter.zod.ts:92 起四槽位为 z.union([z.number(), z.date(), FieldReferenceSchema]),无 string 成立,其上注释写 "Supported data types: Number, Date"
2 日期宏解析器全分支返回 string 成立 —— filter-tokens.ts:303 case 'now': return now.toISOString():319 addUnits(...).toISOString():304-306/321 asYmd(...),无任何分支返回 number 或 Date
3 date-macros.zod.ts 明写 driver 只见 ISO 串 成立 —— "the DRIVER only ever sees ISO date / timestamp strings, never {tokens}"
4 一方调用方一律传 .toISOString() 成立 —— lifecycle-service.ts:1105 / 1134-1135 / 1195 / 1211plugin-email/src/outbox-sweep.ts:155,160

前提全部成立(premise_still_valid: true),方向按分诊结论执行:声明侧对齐现实,不动生产者与调用方。

复核中另找到 issue 未列的第三、第四个一方生产者:

第二条尤其要紧:它是「真实业务拉力」这一轴上可测量的读数,而非断言 —— 另一个包为此付出了 workaround 的代价,且点名 showcase apps 依赖 ISO 字符串。

改动

string 并入四个槽位的联合,并入本文件声明该契约的全部三处:

  • ComparisonOperatorSchema —— 文档面(issue 点名处)。补长 docblock + 每槽位 .describe()
  • FieldOperatorsSchema —— 被强制执行的那一份:NormalizedFilterSchema 拿它校验,导出类型 FieldOperators 由它推断。只改文档面会留下「文档说可以、可达面仍拒绝」的分裂,故一并对齐。这是本 PR 唯一超出 issue 字面点名的范围扩张,理由即此:同一文件、同四个算子、同一份契约的两种拼写。
  • Filter< T > TS 泛型 —— 此处 T 已知,故保持类型精确而非一律放行:Date 字段兼收解析器产出的 ISO 串;string 字段(Field.time'09:00'、autonumber 编码)可排序,而非塌成 never;number 字段仍只收数字。

纯声明侧加宽,additive:未改任何生产者/调用方/driver。各求值面本来就在比较字符串(driver-sql 绑 >/>=/</<=;formula 的 matchesFilter 与 driver-memory 的 matcher 落到 JS 运算符),改前能过的过滤器改后一律仍能过。

第二个 commit:同步被本 PR 改失效的两处注释(纯注释,零代码行)

上面那处 objectql workaround 的注释把旧声明原文写进了正文,加宽后那句话为假。不同步就会让仓库自相矛盾 —— 正是本 issue 要终结的那类腐坏,且是本 PR 自己造成的失效,不是顺手修别的 bug:

git diff 过滤后该 commit 无任何非注释行。

$between(RangeOperatorSchemaFieldOperatorsSchema.$between)带同一处矛盾,但属另一个算子、不在 issue 的测量范围内,按 PD#10 另行记录为 #6571,未夹带修改。

rider ① 裸 z.string() vs 受约束日期串 —— 实测后取z.string()

先与 driver-sql 比较语义对读:四个算子在 sql-driver.ts:7287-7297 下沉为朴素 >/>=/</<=,比较值先过 coerceFilterValue(:6408)。三条实测理由:

  1. 本 schema 是 field-agnostic 的。 它看不到算子作用在哪一列,任何值形状 refine 都是对列类型的猜测。比较值与列的匹配已有归属:coerceFilterValuetemporalFieldKind 分派 —— datetime → storageDatetimeValue,date → toDateOnly,time → canonicalTimeOfDay,其余透传。
  2. ISO refine 会拒掉平台自己声明的形态。 field-value.zod.tsCLOCK_TIME_TYPES 定义 Field.time 值为 HH:MM[:SS[.fff]],并明写 "not Date.parse-able";SqlDriver.temporalFilterValue 正是在比较值位置规范化它('14:30' 变成 '14:30:00',feat(spec,ci): temporal hooks onto the IDataDriver contract; conformance job with live non-UTC servers #3979 契约对,pin 在 sql-driver-time-canonical-storage.test.ts:221-222)。$gte: '09:00' 是受支持的比较,ISO refine 会拒绝它。
  3. date-only 与 full-timestamp 在 driver 侧已经处理一致(rider 要求确认的那一条),故收窄换不到安全性:裸 YYYY-MM-DD下界锚定 UTC 午夜,作上界calendarDayUpperBoundRewrite 改写为半开的 < 次日午夜(dashboard 的日期区间上界打在 datetime 列上丢失当天数据 —— 默认配置即命中 #3777 约定)。

放行面据实写入 .describe() 与 docblock。 加宽同时放行非时间列的文本排序({ code: { $gt: 'M' } })。这是真 SQL 且各后端都会作答,但次序是后端的、不是本契约的:driver-sql 交给方言排序规则(SQLite 按字节、Postgres 按库 locale、MySQL 按列 collation),formula / driver-memory 用 JS 的 UTF-16 码元序;二者仅在 ASCII 上重合 —— 与 StringOperatorSchema 当年不得不裁决的大小写分裂是同一类。

故契约保证的比较值形态是 ISO/时钟那三种:YYYY-MM-DD、UTC ISO-8601 瞬间、HH:MM[:SS[.fff]]。它们 ASCII 定宽,字典序即时间序,各后端一致。排序任意自然语言文本是放行而非承诺

否决窗口:若维护者认为「放行非日期文本排序」代价过高,替代方案是收窄为 ISO date | ISO date-time | HH:MM 时钟 三选一的具名 schema —— 代价是 plugin-auth 的泛型下沉、objectql/showcase 依赖的字符串界、以及任何 autonumber/文本列排序全部落到声明面之外,且需按 ADR-0122 走 PARSED 命名或 isomorphic pin(计数 755 会变)。本 PR 未走这条,理由如上第 1、2 条。

rider ② changeset

.changeset/comparison-operator-string-comparand.md,@objectstack/spec: minor —— 加宽接受面属 additive,按仓内惯例走 minor。(本 PR 有 changeset,故不需要 skip-changeset 标签;Check Changeset 已绿。)

验证读数(全部前台阻塞,flock 串行)

  • spec 全测:pnpm --filter @objectstack/spec testTest Files 343 passed (343) / Tests 8830 passed (8830),408.53s。
  • objectql 受影响用例:engine-filter-array-lowering.test.ts46 passed (46)(先 pnpm --filter '@objectstack/objectql^...' build 备齐依赖,避免把缺产物读成自己改坏了导入)。
  • typecheck:pnpm --filter @objectstack/spec typechecktsc --noEmit + scripts + check:test-typecheck 全绿。测试层 58 files / 267 errors 仍为既有 debt 基线、未增;且 data/filter.test.ts 不在 debt 名单内,故本 PR 的类型断言是真受检的。
  • 生成物:gen:schemagen:docsgen:api-surface 后 —— check:generated 10/10 up to date;check:docs 231 files in sync;check:api-surfacecheck:authorable-surface 均 exit 0。
  • 门禁:check:spec-parsed-alias OK(1465 bare / 755 pinned isomorphic / 710 paired)—— 本改动未新增具名 schema,ADR-0122 计数不动,type-alias-convention.pin.test.ts:1501toHaveLength(755) 无需改;check:nul-bytes OK(6133 文件,无裸控制字节);check:empty-changeset OK(1 declaring changeset);eslint 三个改动文件 exit 0。
  • authorable-surface.base.json 已撤回:gen:authorable-surface-base 产出的 diff 全是他人的键(api/ValidateData*cloud/ProvisionEnvironmentResponse:hostnameAssignment),是锚点追赶 main,与本改动无关。生成器与 check:authorable-surface 都写明「重锚是需要独立 review 的刻意动作,绝非本次构建的副作用」(check:authorable-surface--check 模式下也会重写 authorable-surface.base.json —— 一次纯核验会改工作区,且任何无关 PR 都能因此静默推进删除门的锚点 #5358)。本改动不新增 authorable 键。

逆向验证(方向先判后跑)

判定:本改动是加宽,新测试钉的是「新被接受」的值,故预期为常规方向 —— 撤掉 schema 改动则新测试转红。实测与预判一致。

  • 撤回 filter.zod.ts、保留新测试:filter.test.ts5 failed | 101 passed (106),报错正是 "Invalid input: expected number, received string" / "expected date, received string"101 个既有断言仍绿 —— 这同时证明加宽是 additive,没有改变任何原有判定。
  • TS 半边由 tsc 而非 vitest 裁定(vitest 不做类型检查),故单独跑 tsc -p tsconfig.test.json,得两条、均落在新增断言块内:
    filter.test.ts(547,57): error TS2322: Type 'string' is not assignable to type 'Date'.
    filter.test.ts(549,54): error TS2322: Type 'string' is not assignable to type 'undefined'.
    
    第二条读作 undefined 而非 never:旧 guard 的 never 撞上槽位自身的 ?,optional 的 neverundefined测试注释已按实测原文更正(此前按预期写的是 never)—— 报告模板不该压过真实读数。
  • 恢复改动后:filter.test.ts 106/106 绿,typecheck 全绿。

四个排序比较槽位声明为 `number | Date | FieldReference`,而平台自己的生产者
往这四个槽位里放的恰恰只有字符串。这不是"声明得不够细",是声明与现实相矛盾。

## 前提复核(origin/main @ 02479cc,逐条核对 issue 事实)

1. `packages/spec/src/data/filter.zod.ts:92` 起四槽位确为
   `z.union([z.number(), z.date(), FieldReferenceSchema])`,无 `string`;
   其上注释写 "Supported data types: Number, Date"。成立。
2. `packages/core/src/utils/filter-tokens.ts` 日期宏全分支返回 string —
   `:303 case 'now': return now.toISOString()`、`:319 addUnits(...).toISOString()`、
   `:304-306/321 asYmd(...)`,无任何分支返回 number 或 Date。成立。
3. `date-macros.zod.ts` 明写 "the DRIVER only ever sees ISO date / timestamp
   strings, never {tokens}"。成立。
4. 一方调用方传 `.toISOString()`:`lifecycle-service.ts:1105/1134-1135/1195/1211`、
   `plugin-email/src/outbox-sweep.ts:155,160`。成立。
   复核中另发现**第三个**一方生产者(issue 未列):
   `plugin-auth/src/objectql-adapter.ts:206-215` 用 better-auth 未定型的
   `condition.value` 直接下沉到这四个槽位。

前提全部成立,方向按分诊结论:声明侧对齐现实,不动生产者与调用方。

## 改动

`string` 并入四个槽位的联合,并入本文件声明该契约的**全部三处**:

- `ComparisonOperatorSchema` —— 文档面(issue 点名处),补长 docblock 与
  每槽位 `.describe()`;
- `FieldOperatorsSchema` —— **被强制执行的那一份**:`NormalizedFilterSchema`
  拿它校验,导出类型 `FieldOperators` 由它推断。只改文档面会留下
  "文档说可以、可达面仍拒绝"的分裂,故一并对齐;
- `Filter<T>` TS 泛型 —— 此处 `T` 已知,故保持类型精确而非一律放行:
  `Date` 字段兼收解析器产出的 ISO 串,`string` 字段(`Field.time` 的 `'09:00'`、
  autonumber 编码)可排序而非塌成 `never`,`number` 字段仍只收数字。

纯声明侧加宽,additive:未改任何生产者/调用方/driver。各求值面本来就在比较
字符串(driver-sql 绑 `>`/`>=`/`<`/`<=`;formula 的 matchesFilter 与
driver-memory 的 matcher 落到 JS 运算符),改前能过的过滤器改后一律仍能过。

## rider ① 裸 string vs 受约束日期串 —— 实测后取**裸 `z.string()`**

先与 driver-sql 比较语义对读:`$gt/$gte/$lt/$lte` 在
`sql-driver.ts:7287-7297` 下沉为朴素 `>`/`>=`/`<`/`<=`,比较值先过
`coerceFilterValue`(`:6408`)。三条实测理由:

1. **本 schema 是 field-agnostic 的** —— 它看不到算子作用在哪一列,任何
   值形状 refine 都是对列类型的猜测。比较值与列的匹配已有归属:
   `coerceFilterValue` 按 `temporalFieldKind` 分派(datetime →
   `storageDatetimeValue`,date → `toDateOnly`,time → `canonicalTimeOfDay`,
   其余透传)。
2. **ISO refine 会拒掉平台自己声明的形态。** `field-value.zod.ts` 的
   `CLOCK_TIME_TYPES` 定义 `Field.time` 值为 `HH:MM[:SS[.fff]]`,并明写
   "not Date.parse-able";`SqlDriver.temporalFilterValue` 正是在**比较值位置**
   规范化它(`'14:30'` → `'14:30:00'`,#3979 契约对,pin 在
   `sql-driver-time-canonical-storage.test.ts:221-222`)。`$gte: '09:00'` 是
   受支持的比较,ISO refine 会拒绝它。
3. **date-only 与 full-timestamp driver 侧已经处理一致**(rider 要求确认的
   那一条),故收窄换不到安全性:裸 `YYYY-MM-DD` 作下界锚定 UTC 午夜,作上界
   由 `calendarDayUpperBoundRewrite` 改写为半开的 `< 次日午夜`(#3777 约定)。

**放行面据实写入 `.describe()` 与 docblock**:加宽同时放行非时间列的文本排序
(`{ code: { $gt: 'M' } }`)。这是真 SQL 且各后端都会作答,但**次序是后端的、
不是本契约的** —— driver-sql 交给方言排序规则(SQLite 按字节、Postgres 按库
locale、MySQL 按列 collation),formula/driver-memory 用 JS 的 UTF-16 码元序;
二者仅在 ASCII 上重合。故契约**保证**的比较值形态是 ISO/时钟那三种
(`YYYY-MM-DD`、UTC ISO-8601 瞬间、`HH:MM[:SS[.fff]]`):它们是 ASCII 定宽,
字典序即时间序,各后端一致。排序任意自然语言文本是"放行"而非"承诺"。

## rider ② changeset

`.changeset/comparison-operator-string-comparand.md`,`@objectstack/spec: minor`
—— 加宽接受面属 additive,按仓内惯例走 minor。

## 验证读数(全部前台阻塞,flock 串行)

- `pnpm --filter @objectstack/spec test`:343 files / **8830 passed**,408.53s。
- `pnpm --filter @objectstack/spec typecheck`:`tsc --noEmit` + scripts +
  `check:test-typecheck` 全绿(测试层 58 files / 267 errors 仍为既有 debt 基线,
  未增;`data/filter.test.ts` **不在** debt 名单内,故本 PR 的类型断言是真受检的)。
- 生成物:`gen:schema` → `gen:docs` → `gen:api-surface` 后
  `check:generated` **10/10 up to date**;`check:docs` 231 files in sync;
  `check:api-surface`、`check:authorable-surface` 均 exit 0。
- 门禁:`check:spec-parsed-alias` OK(1465 bare / **755 pinned isomorphic** / 710
  paired —— 本改动未新增具名 schema,ADR-0122 计数不动,pin 测试
  `type-alias-convention.pin.test.ts:1501` 的 `toHaveLength(755)` 无需改);
  `check:nul-bytes` OK(6132 文件,无裸控制字节);eslint 两个改动文件 exit 0。
- `gen:authorable-surface-base` 产出的 `authorable-surface.base.json` 变更已
  **撤回**:其 diff 全是他人的键(`api/ValidateData*`、
  `cloud/ProvisionEnvironmentResponse:hostnameAssignment`)——是锚点追赶 main,
  与本改动无关。生成器与 `check:authorable-surface` 都写明"重锚是需要独立
  review 的刻意动作,绝非本次构建的副作用"(#5358)。本改动不新增 authorable 键。

## 逆向验证(方向先判后跑)

判定:本改动是**加宽**,新测试钉的是"新被接受"的值,故预期为常规方向 ——
撤掉 schema 改动则新测试转红。实测与预判一致:

- 撤回 `filter.zod.ts`、保留新测试:`filter.test.ts` **5 failed | 101 passed**,
  报错正是 `"Invalid input: expected number, received string"` /
  `"expected date, received string"`。101 个既有断言仍绿 —— 这同时证明加宽是
  additive,没有改变任何原有判定。
- TS 半边由 `tsc` 而非 vitest 裁定(vitest 不做类型检查),单独跑
  `tsc -p tsconfig.test.json` 得两条,均落在新增断言块内:
  `filter.test.ts(547,57): TS2322 Type 'string' is not assignable to type 'Date'`
  `filter.test.ts(549,54): TS2322 Type 'string' is not assignable to type 'undefined'`
  第二条读作 `undefined` 而非 `never`:旧 guard 的 `never` 撞上槽位自身的 `?`,
  optional 的 `never` 即 `undefined`。测试注释已按**实测原文**更正
  (原先按预期写的是 `never`)。
- 恢复改动后:`filter.test.ts` 106/106 绿,typecheck 全绿。

Fixes #5685

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

vercel Bot commented Aug 8, 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 8, 2026 5:28am

Request Review

@github-actions github-actions Bot added the size/m label Aug 8, 2026
@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/objectql, @objectstack/spec.

115 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 @objectstack/objectql, 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 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/tenancy-modes.mdx (via @objectstack/spec)
  • 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 @objectstack/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx (via packages/objectql, @objectstack/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 @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/permissions/authentication.mdx (via @objectstack/objectql)
  • 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/objectql, @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/objectql, @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/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/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/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/apps.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/field-grouping-and-order.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.

实现 #5685 时在 `packages/objectql` 发现**两处注释直接引用了被本 PR 改掉的声明**,
不同步就会让仓库自相矛盾 —— 正是本 issue 要终结的那类 declared ≠ documented 腐坏。
纯注释,零代码行改动(`git diff` 过滤后无非注释行)。

## 为什么这不是范围外夹带

不是"顺手修别的 bug",是**本 PR 自己造成的失效**:两处注释都把
`$gt` 的声明原文写进了正文,而本 PR 把那份声明改了。

- `packages/objectql/src/filter-comparand-shape.ts:214-218`(#5869)原文写:
  「`FieldOperatorsSchema` cannot be used as the gate directly because it is
  stricter than the runtime in ways the runtime deliberately allows —— `$gt` is
  declared `number | Date | FieldReference`, while `['created_at', '>',
  '2026-01-01']` lowers to a STRING bound that every backend accepts and that
  the showcase apps rely on」。加宽后这句话为假。
  改法:保留该 gate 不整表 parse 的**成本**理由(它每次读写都跑),把已被
  修正的"schema 比运行时严"那条降级为 #5685 的历史记录 —— 记录而非删除,
  因为这处 workaround 本身就是结论"错的是 schema 不是运行时"的证据。
- `packages/objectql/src/engine-filter-array-lowering.test.ts:520-523` 同样引用
  旧联合,同步为 `number|Date|string|FieldReference`,并点明该 gate 仍只管三个
  list 声明是出于成本、而非"schema 不同意"。

## 反向补强 spec 侧 docblock

objectql 这处 workaround 同时是 rider ① 需要的**实测**证据(而非断言):
另一个包为绕开本声明而构建,并白纸黑字写下它是错的,且点名
**showcase apps 依赖 ISO 字符串** —— 这是 issue 与 PR 都未列出的**第四个**
一方生产者,也是"真实业务拉力"这一轴上可测量的读数。已并入
`ComparisonOperatorSchema` docblock。

## 验证读数

- `packages/objectql` 受影响用例:`engine-filter-array-lowering.test.ts`
  **46 passed (46)**(先 `pnpm --filter '@objectstack/objectql^...' build` 备齐依赖)。
- `packages/spec`:`filter.test.ts` **106/106 绿**;`check:generated` 仍
  **10/10 up to date**(注释不入产物)。
- eslint 三个改动文件 exit 0;`check:nul-bytes` OK(6133 文件)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY
@qq9340100
qq9340100 marked this pull request as ready for review August 8, 2026 06:09
@qq9340100
qq9340100 added this pull request to the merge queue Aug 8, 2026
Merged via the queue into main with commit 5b4780b Aug 8, 2026
27 checks passed
@qq9340100
qq9340100 deleted the claude/issue-5685-comparison-operator-string branch August 8, 2026 06:26
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/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ComparisonOperatorSchema 的 $gt/$gte/$lt/$lte 不含 string,与平台自己只产出字符串的日期宏解析器相矛盾

2 participants