feat(lint): refuse a MISSPELLED format in a json_schema validation rule at publish time (#5178) - #5490
Merged
Merged
Conversation
…n rule at publish time (#5178) #5029 registered `ajv-formats` so a `json_schema` rule's `format` is really enforced. Under `strict: false` an UNRECOGNISED format name stays a non-event: ajv logs one line at compile time and DROPS the keyword, so `format: 'emial'` leaves the rule declared, listed, running on every write, enforcing `type` and `required` — and enforcing nothing for the keyword its author wrote. The record is ACCEPTED, which is the silent direction. New gating rule `validateRuleSchemaFormats` (`validation-rule-json-schema-unknown-format`), one entry in AUTHORING_RULES so `os validate` / `os build` / `os lint` all run it. Each finding names the rule, the object, the RFC 6901 JSON Pointer to the keyword and the nearest registered name. The vocabulary is enumerated off a live instance of the same ajv the publish gate builds to mirror the runtime (`registeredFormatNames()`), never a hardcoded list that would start refusing formats the write path enforces the day the plugin adds one. The walk visits only real subschema positions (`properties`, both `items` forms, `anyOf`/`allOf`/`oneOf`/`prefixItems`, `$defs`/`definitions`, `additionalProperties`, `patternProperties`, `if`/`then`/`else`, `not`, `contains`, `propertyNames`, `dependentSchemas`, draft-07 `dependencies`) at any depth, plus a `conditional`'s `then`/`otherwise` rules. A `format` inside `default`/`const`/`enum`/`examples` is data ajv never reads as schema, so it is deliberately not reported; a non-string `format` is left to `validation-rule-json-schema-uncompilable`. The runtime is untouched and the #4762/#5029 compile parity is untouched: this judgement is laid BESIDE the compile, never folded into it. `validateRuleCompilability` still compiles in the runtime's exact environment and still publishes a typo'd format, and its #5029 pin passes verbatim. Both rules share one traversal (`walkObjectValidationRules`) and one ajv environment, so they cannot disagree about which rules exist or what "registered" means. `ajv`/`ajv-formats` stay lazy — and this rule is lazier: a `json_schema` rule naming no `format` loads neither. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GX3sL71LFq8m2usg6VqTSE
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
marked this pull request as ready for review
August 5, 2026 14:17
os-zhuang
enabled auto-merge
August 5, 2026 14:17
This was referenced Aug 5, 2026
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 #5178
前提复核(先证实,再动手)
按 os-dev 规程,先在合并后的
origin/main上用仓库自己的依赖重跑了 issue 的 repro,确认前提仍然成立:ajv@8.20.0+ajv-formats@3.0.1,运行时的选项原样:json_schemavalidation rule'sformatkeyword is silently IGNORED — ajv runs withoutajv-formats, soformat: 'email'enforces nothing #5029/PR fix(objectql,lint)!:json_schema规则的format关键字真正生效 —— 注册 ajv-formats (#5029) #5182 已在 main:packages/objectql/src/validation/rule-validator.ts:267是addFormats(ajv),packages/lint/src/validate-rule-compilability.ts的 parity 也已跟上。strict: false下 ajv 只在编译期打一行日志就丢掉这个关键字,于是规则声明了、出现在元数据里、每次写入都跑、type/required照样生效 —— 而作者真正写下的那个约束一条记录都不管。失败方向是记录被接受,所以下游也没有任何信号。前提成立,按裁定的方案 1 实施。
做了什么
新增 gating 规则
validateRuleSchemaFormats(packages/lint/src/validate-rule-schema-formats.ts),规则 idvalidation-rule-json-schema-unknown-format,在AUTHORING_RULES里一行接入,os validate/os build/os lint三个命令自动拾取(接入方式与validate-rule-compilability完全一致:tier: 'gating'/input: 'parsed'/commands: ALL/surfaces: CLI_ONLY+RUNTIME_OBJECT_WRITES_P2)。每条 finding 点名:规则名、对象名、RFC 6901 JSON Pointer、以及最近似的合法名:
format 注册集:从同一注册路径枚举,不硬编码
registeredFormatNames()读的是createRuntimeAjv()这个活实例的ajv.formats键集 —— 也就是本仓库门禁为镜像运行时而构造的那台 ajv,其选项与插件注册由既有 parity 测试对着rule-validator.ts的源码钉住。硬编码清单会是第三份意见,而且是唯一没人会去更新的那份:插件哪天加了一个名字,清单就开始拒收写入路径真正会执行的 format —— 正是"门禁把好元数据判红,于是被关掉,于是什么都不保护"这条失败模式,只不过枪口对着门禁自己。枚举则让门禁在插件升级时自动跟随,一行都不用改。顺带回答了派发单里"含 fast 模式差异如有"这一问:实测两种模式注册的名字集合完全相同(26 个),只有实现不同 —— 而即便哪天不再相同,实例仍然知道,清单仍然不会知道。
遍历是 JSON-Schema 感知的,因为
format不是魔法词朴素的"收集任意深度的每一个
format键"会在数据上产生假阳性,而假阳性是本条验收的红线。所以只下潜到 JSON Schema 定义为子 schema 的位置:properties、items(2020-12 的 schema 形态与 draft-07 的 tuple 数组两种)、anyOf/allOf/oneOf/prefixItems、$defs/definitions、additionalProperties、patternProperties、if/then/else、not、contains、propertyNames、dependentSchemas、draft-07dependencies,任意深度;外层还覆盖conditional的then/otherwise子规则(evaluateRule会递归进去,撞的是同一个checkJsonSchema)。反过来,持有任意数据的位置一律不读:
default/const/enum/examples里的formatajv 从不当 schema 解释,本来就不生效、也从来不是要它生效 —— 报它等于凭一份合法文档凭空发明一个缺陷。非字符串的format(如format: 42)交给validation-rule-json-schema-uncompilable,那条已经用 ajv 自己的话拒收了(data/properties/e/format must be string),这里再报一遍只是噪音。运行时没动;#4762/#5029 的编译 parity 也没动
方案 2 出局的理由在实现里落成了结构:这条判断叠在编译旁边,不是折进编译里。
validateRuleCompilability依旧在运行时的精确环境里编译、依旧放行拼错的 format(因为在那里它确实编译得过),它的 #5029 钉子断言一字未改,只加了注释说明拒收现在由谁给出。两条规则回答两个不同的问题 —— "ajv 接受这份 schema 吗?"与"这个
format关键字会起作用吗?" —— 并共享同一次遍历(walkObjectValidationRules)和同一台 ajv,所以它们不可能对"有哪些规则"或"什么叫已注册"产生分歧。ajv/ajv-formats保持惰性,而且本条比邻居更惰:只有当 schema 真的写下了某个format名字时才去取注册集,所以一条不含format的json_schema规则两个包都不加载 ——lazy-deps.test.ts的三层(结构扫描、两个 dist 子进程探针、进程内行为)都钉了这一点。反向验证:方向是先判定、再运行的
这条不是"把删掉的肢体接回去看诊断变红"那一类。金丝雀方向在写之前就定了:合并进编译门禁会让 publish gate 与写入路径对"什么能编译"产生分歧 —— 而那正是
validate-rule-compilability.ts存在的唯一理由。所以正确的预期是 旧门禁在同一份 fixture 上保持绿,新规则在它旁边判红:这条断言若哪天变红,说明两个判断被合并了、parity 契约已破 —— 测试里写明了这层含义。另有一例钉住"schema 既编译不过、又拼错 format"时两个判决各出一条、各归其主。
假阳性红线 + 消费半径清扫
[],并同时断言 Aformatrule with an invalid regex, and ajson_schemarule ajv cannot compile, still fail OPEN — the same trap #4649 closed, one rule type over #4762 编译门禁也[]—— 免得这张表是因为元数据在别处坏掉而"空着绿"。format键的 schema、其余五种 validation 规则、无schema的规则、以及undefined/null/数字/字符串/数组等垃圾输入,全部绿且不抛。examples/、packages/cli、packages/metadata-protocol、apps/、content/里唯一真实存在的json_schema校验规则是examples/app-showcase/src/data/objects/account.object.ts的support_config_shape,其 schema 不含任何format键;用构建产物对它原样跑过一遍,两个门禁都是[]。所以本门禁不可能把任何既有元数据判红。packages/lint的 validate-rule-compilability.ts:239 也有同形的obj.validations ?? obj.validationRules别名读法(#5017 全包 grep 的第八处,第三个文件) #5096 元测试(源码扫描"只读 spec 声明的键")因共享句柄引入了walked/names两个新累加器,按该测试的设计把它们加进PLUMBING白名单并写明理由(数组的.push/.length/.sort,不是元数据键)。验证
pnpm --filter @objectstack/lint test(合并 main 后)pnpm --filter @objectstack/lint typechecktsc --noEmit无输出)pnpm --filter @objectstack/cli testpnpm --filter @objectstack/metadata-protocol testnode scripts/check-nul-bytes.mjsgrep -naP自扫check:doc-authoring/check:published-files/check:type-check-coveragepackages/lint是发布包,已附非空 changeset(.changeset/json-schema-rule-unknown-format-gate.md),含 FROM/TO 式的升级说明:门禁若新拒了某条规则,那个 format 名从来就没被执行过,改对拼写或换pattern即可,没有任何原本在工作的元数据会改变行为。🤖 Generated with Claude Code
https://claude.ai/code/session_01GX3sL71LFq8m2usg6VqTSE
Generated by Claude Code