fix(objectql,lint)!: json_schema 规则的 format 关键字真正生效 —— 注册 ajv-formats (#5029) - #5182
Merged
Conversation
ajv 8 不内置 `format` —— 它在独立的 `ajv-formats` 包里。运行时的共享实例只有
`new Ajv({ allErrors: true, strict: false })`,而 `strict: false` 下未注册的
format 不是错误:ajv 只打一行日志,然后**丢弃该关键字**。
于是一条 `json_schema` 规则可以编译成功、在每次写入时运行、强制 `type` 与
`required`,却对 `format` 什么都不做 —— 对每一条记录,永远如此。唯一的信号是
编译期一行不指名任何规则、任何对象的 stderr。这是 #4649 / #4762 的同一族,再
往里一层,而且更阴:失败是**部分的**,规则在开发时会拒绝错误的 `type` 负载,
读起来像在工作,而 `format` 那一半从未触发。
改动:
- `@objectstack/objectql` 依赖并注册 `ajv-formats`(`addFormats(ajv)`),取
**默认(full)** format 集 —— `fast` 模式恰好在作者最常用的那几个 format 上
拿正确性换速度,一个"大致匹配"的 format 只是同一个「声明 ≠ 强制」缺陷换了个
小一点的洞。
- `@objectstack/lint` 的 #4762 发布门禁与运行时**同一个 ajv 环境**编译,因此同步
注册同一个插件。这不是装饰性对齐:`ajv-formats` 还会注册
`formatMinimum` / `formatMaximum`,没有插件时它们是未知关键字(`strict: false`
⇒ 静默忽略),门禁会放行一条运行时随后拒绝编译的 schema —— 规则过审却什么也
不强制,正是该门禁存在的理由。parity 测试现在从运行时源码里读出插件注册,两
边不可能再无声漂移。
作者面不变:`format` 仍是合法、可发布的标准 JSON Schema 关键字(#5029 正文的
方案 2「发布期拒收 format」经权衡后未采纳)。变的只是声明now为真。
已知残留边界(本 PR 刻意不改、并以测试钉住):拼错的 format 名仍被忽略 ——
`format: 'emial'` 在 `strict: false` 下编译通过、什么都不强制,运行时与门禁一致。
单独记在 #5178。
Fixes #5029
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pbu27iNUfQCHeuS551Rqo7
…-formats-enforced
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 16 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
This was referenced Aug 4, 2026
os-zhuang
marked this pull request as ready for review
August 4, 2026 09:14
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5029
按 PM 裁定取正文方案 1:运行时注册
ajv-formats,让format真正被强制。缺陷
ajv 8 不内置
format—— 它在独立的ajv-formats包里。运行时的共享实例只有:而
strict: false下未注册的 format 不是错误:ajv 只打一行日志,然后丢弃该关键字。于是一条
json_schema规则可以编译成功、在每次写入时运行、强制type与required,却对format什么都不做 —— 对每一条记录,永远如此。唯一的信号是编译期一行不指名任何规则、也不指名任何对象的 stderr。这是 #4649 / #4762 的同一族,再往里一层,而且更阴:失败是部分的。规则在开发时会老老实实拒绝
type错误、required缺失的负载,读起来像在正常工作,而format那一半从未触发过。format又恰好是 JSON Schema 里最常被伸手去拿的关键字之一(email、uri、uuid、date、date-time、ipv4),所以这不是什么冷僻角落 —— 它正是 AI 写元数据时第一个会写下的形状。改动
packages/objectql/src/validation/rule-validator.ts—— 依赖并注册ajv-formats:取 默认(full) format 集,是个刻意的选择:
fast模式恰好在作者最常用的那几个 format 上拿正确性换速度,而一个「大致匹配」的 format 只是同一个「声明 ≠ 强制」缺陷换了个小一点的洞。packages/lint/src/validate-rule-compilability.ts(声明过的跨域 devx 最小触碰) —— #4762 的发布门禁与运行时同一个 ajv 环境编译,这是它的设计前提,所以它同步注册同一个插件(同样惰性加载:ajv-formats会require('ajv/dist/compile/codegen'),急切 import 等于把 ajv 从后门拖上内核启动路径)。这一步不是装饰性对齐。
ajv-formats还会注册formatMinimum/formatMaximum两个关键字:{ format: 'date', formatMinimum: 42 }strict: false)formatMinimum value must be ["string"]也就是说:门禁若不装插件,就会放行一条运行时随后拒绝编译的 schema —— 规则过了审、进了元数据、然后对每一条记录什么都不强制,正是该门禁存在的全部理由。parity 测试现在从运行时源码里读出
addFormats(ajv)这行注册,两边不可能再无声漂移。作者面不变。
format仍是合法、可发布的标准 JSON Schema 关键字;正文的方案 2(发布期拒收format)经权衡后未采纳 —— 拒收标准 JSON Schema 会把作者推向私有写法。变的只是「声明」现在为真。changeset 里已响亮写明。要点:
format的json_schema规则,今天能过的写入,升级后可能被拒 —— 包括 flow、seed、导入和集成写入,不只是 UI。type: 'json_schema'且 schema 内任意层级(含$defs/$ref、conditional的then/otherwise)带format的规则找出来,逐个核对存量列;若某个 format 只是「愿景」而非事实,先删掉再升级 —— 删除这个关键字从此是个有意义、看得见的动作,而不再是空操作。仓内扫描结果:没有任何 in-tree 的
json_schema规则携带format关键字(showcase 的support_config_shape用的是enum/minimum;packages/lint测试里那条format-bearing fixture 正是「门禁照常放行」的用例)。因此本次改动不需要任何语义抉择,examples 与存量测试全绿。刻意保留的残留边界
拼错的 format 名仍然被忽略。
format: 'emial'在strict: false下编译通过 —— ajv 打一行unknown format "emial" ignored然后丢掉它 —— 运行时与门禁行为一致,所以一个拼写错误依然什么都不强制。本 PR 不改这个行为,而是在两个包里都用测试钉住它:让它成为一条有记录的、刻意的边界,而不是一个疏漏;将来要翻它,就必须是一次会让这些测试变红的自觉动作。门禁这边尤其不能自作主张 —— 门禁若给出一个运行时并不认同的判决,那就是
strict: true那个错误换了顶帽子。单独记为 #5178(附Blocked-by: #5029)。验证
新增测试(运行时侧,
rule-validator.test.ts›json_schema — format is actually enforced (#5029)):format: 'email'拒not-an-email/ 收ops@objectstack.ai;uuid/date-time/uri各一正一反;$ref与数组元素内的嵌套 format;JSON 字符串值这条路径同样强制;以及拼错 format 名的现状钉子。门禁侧(
validate-rule-compilability.test.ts› parity):从运行时源码读出addFormats(ajv)(并断言是无参调用 = 默认 full 集);带format的 schema 照常放行;拼错的 format 名照常放行;formatMinimum的合法/非法两例证明插件那一半 parity 确实承重。启动路径契约(
lazy-deps.test.ts/runtime-lazy-deps.test.ts)把ajv-formats加进LAZY_DEPS:判format规则时两者都不加载,第一条json_schema规则才付出代价。🤖 Generated with Claude Code
https://claude.ai/code/session_01Pbu27iNUfQCHeuS551Rqo7
Generated by Claude Code