fix(lint): regex/JSON Schema 编译不过的校验规则改在发布期拒绝 —— 声明了却什么都不拦的两个静态产物 (#4762) - #5027
Conversation
…ot compile (#4762) A `format` rule's `regex` that `new RegExp(...)` throws on, and a `json_schema` rule's `schema` ajv cannot compile, are both logged and SKIPPED on the write path, so the rule is declared, listed and enforces nothing on every record. Both faults are STATIC — decidable from the metadata alone — so they are rejected at authoring/publish time by a new gating rule, `validateRuleCompilability`, wired into AUTHORING_RULES (all three commands). Detection uses the real compilers: `new RegExp(source)` for the regex, and ajv constructed with the SAME options the runtime's shared instance uses (`{ allErrors: true, strict: false }`), pinned against `rule-validator.ts`'s own source so the two cannot drift. `packages/objectql` is deliberately untouched: the `#4649 — unchanged neighbours` pins and the module header stand as the honest record that the RUNTIME half is still fail-open. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NrmBxj8rK2uGCnh9aipjwX
…tic-rule-publish-gate # Conflicts: # packages/lint/src/authoring-rules.ts
…tic-rule-publish-gate
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 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:
|
|
📌 范围外发现,已按 Prime Directive #10 单独立单,未在本 PR 修复:
它是同一家族(#4649 → #4762)再往里一层,但方向相反 —— schema 编译得过,只是有个关键字被丢了 —— 所以本 PR 的 parity 设计(用运行时同款 ajv 参数编译)按定义抓不到它,那是那道门的正确行为。#5029 里列了三个选项(注册 Generated by Claude Code Generated by Claude Code |
|
OSV scanner 报的是 已按 Prime Directive #10 立单:#5032(与 #4965 不同 —— #4965 问的是"无可用修复的告警怎么表达",这 8 条 scanner 明确写了 Generated by Claude Code Generated by Claude Code |
…walk (#4762) `os lint` never parses, so the walker is handed whatever object the author's own module built — and `const r = {…}; r.then = r` is a two-line accident that would hang the lint. Same promise `flow-walk.ts`'s `MAX_REGION_DEPTH` makes, with a non-vacuous test: a broken regex parked at the bottom of a legal nest is still reported, one level past the cap is where the walker stops looking. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NrmBxj8rK2uGCnh9aipjwX
|
➕ 追加一个提交
测试是非空的:自引用规则不再挂死且返回
Generated by Claude Code Generated by Claude Code |
…tic-rule-publish-gate
Fixes #4762
问题
对象校验规则的六种类型里,有两种带着静态产物,写入路径把它交给真正的编译器,外面套一层
try/catch,catch 里打一行日志然后return null:format的regex→new RegExp(rule.regex)→ "Validation rule '…' has an invalid regex — skipped"json_schema的schema→ajv.compile(rule.schema)→ "Validation rule '…' has an uncompilable JSON Schema — skipped""skipped" 的意思是:规则被声明了、出现在元数据里、出现在任何"这个对象受什么保护"的清单里 —— 然后对每一条记录都不拦任何东西,只要这份元数据还部署着就一直如此,唯一的信号是一行没人看的 WARN 日志。这正是 #4649 描述的形状,只是换了一种规则类型;#4761 把 CEL 谓词(
script/cross_field/conditional.when)翻成了 fail-closed,并刻意留下了这两个,因为它们的爆炸半径不一样(见下)。做法:只走路线 1(作者期/发布期拒绝)
PM 裁定:本 PR 只做 issue 里的路线 1。新增一条
gating规则validateRuleCompilability,登记进AUTHORING_RULES,因此三个作者期命令(os validate/os build/os lint)一次性全覆盖,不需要任何按命令的手工接线。两个诊断 id:validation-rule-regex-uncompilableformat规则的regex在new RegExp(...)里抛错validation-rule-json-schema-uncompilablejson_schema规则的schema在ajv.compile(...)里抛错每条 finding 点名规则名、对象名、配置路径,并原样带上编译器自己的错误文本 —— 作者对着 "invalid regex" 无从下手,对着
Invalid regular expression: /([/: Unterminated character class才能改。嵌在conditional的then/otherwise里的规则同样被判(evaluateRule会递归进去并抵达同一个 checker),finding 会指出是哪一条分支。判定用的是真编译器,不是"用一个模式去判另一个模式"
用正则去解析正则、或手写一遍 JSON Schema 的类型检查,等于对"能不能编译"造出第二种意见,而它只会和运行时的那一份漂移 —— 那就是在上一层新开一个 declared ≠ enforced 的口子。所以:
new RegExp(source)编译 —— 就是checkFormat发出的那一次调用,不带 flag;{ allErrors: true, strict: false })。这组参数由一条 parity 测试从
rule-validator.ts的源码里读回来比对,所以哪天有人改了运行时的 ajv 参数,是这条测试变红,而不是两边悄悄开始各说各话。strict: false在两个方向上都吃重:它是作者写的 schema 能带 vendor 关键字的原因,一个跑strict: true的门会拒掉写入路径明明编得过的元数据 —— 一个会把正常元数据判红的门,最后会被关掉,然后什么都保护不了。测试里专门有这一例。ajv是@objectstack/lint的新依赖,懒加载:只有真的声明了json_schema校验规则的 stack 才会付这个代价,和typescript/sucrase一样由本包的lazy-deps.test.ts钉住;内核启动路径(@objectstack/lint/runtime)则一次都不会加载它 —— 这一条本 PR 把它加进了runtime-lazy-deps.test.ts的LAZY_DEPS,所以将来若把这条规则放宽到runtime-publish,是这条断言先出声。运行时那一半是刻意不动的
按 PM 裁定,路线 2 不在本 PR 范围内:
packages/objectql/**零改动。rule-validator.ts仍然对这两者 fail-open,rule-fail-closed.test.ts里#4649 — unchanged neighbours的钉子和模块头那段 "Deliberately NOT changed here" 原样保留 —— 它们是"运行时这一半仍然 fail-open"的诚实记录,不该被本 PR 抹掉。formatrule with an invalid regex, and ajson_schemarule ajv cannot compile, still fail OPEN — the same trap #4649 closed, one rule type over #4762 上给维护者定。另外零改动的还有:
packages/spec/**及其生成物、packages/metadata-protocol/src/protocol.ts、content/docs/releases/。分支相对main的 delta 就是本 PR 的 9 个文件。验证
本地全跑,证据见下(均在合并
origin/main之后重跑过一次):pnpm --filter @objectstack/lint test→ 57 files / 1079 tests passedpnpm --filter @objectstack/lint typecheck→ 干净;pnpm typecheck(全仓)→ 123 tasks successfulpnpm lint(ESLint 全仓)→ 通过pnpm build(turbo 全仓)→ 71 tasks successfulpnpm --filter @objectstack/spec check:generated→ All 8 generated artifacts are up to datecheck:nul-bytes/check:doc-authoring/check:adr-anchors/check:release-notes/check:published-files/check:type-check-coverage/check:engine-double-contract/check:merge-driver/check:node-version/check:i18n/check:i18n-coverage/check:docs-audit-scope/check:role-word/check:org-identifier/check:slot-lookup—— 全部 PASS@objectstack/cli(68 files / 600 tests)、@objectstack/metadata-protocol(34 files / 301 tests)全绿packages/objectql的rule-fail-closed.test.ts→ 22 tests passed出厂样例仍然干净(非空验证 —— showcase 的
account对象本来就带一条format+ regex 规则和一条json_schema规则):真 CLI 上的端到端拒绝(临时 demo stack,三个命令都 exit 1):
Changeset:
.changeset/rule-compilability-publish-gate.md(@objectstack/lintpatch)。🤖 Generated with Claude Code
https://claude.ai/code/session_01NrmBxj8rK2uGCnh9aipjwX
Generated by Claude Code