Skip to content

fix(lint): regex/JSON Schema 编译不过的校验规则改在发布期拒绝 —— 声明了却什么都不拦的两个静态产物 (#4762) - #5027

Merged
xuyushun441-sys merged 5 commits into
mainfrom
claude/issue-4762-static-rule-publish-gate
Aug 4, 2026
Merged

fix(lint): regex/JSON Schema 编译不过的校验规则改在发布期拒绝 —— 声明了却什么都不拦的两个静态产物 (#4762)#5027
xuyushun441-sys merged 5 commits into
mainfrom
claude/issue-4762-static-rule-publish-gate

Conversation

@xuyushun441-sys

Copy link
Copy Markdown
Contributor

Fixes #4762

问题

对象校验规则的六种类型里,有两种带着静态产物,写入路径把它交给真正的编译器,外面套一层 try/catch,catch 里打一行日志然后 return null:

  • formatregexnew RegExp(rule.regex)"Validation rule '…' has an invalid regex — skipped"
  • json_schemaschemaajv.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:

id 触发条件
validation-rule-regex-uncompilable format 规则的 regexnew RegExp(...) 里抛错
validation-rule-json-schema-uncompilable json_schema 规则的 schemaajv.compile(...) 里抛错

每条 finding 点名规则名、对象名、配置路径,并原样带上编译器自己的错误文本 —— 作者对着 "invalid regex" 无从下手,对着 Invalid regular expression: /([/: Unterminated character class 才能改。嵌在 conditionalthen / otherwise 里的规则同样被判(evaluateRule 会递归进去并抵达同一个 checker),finding 会指出是哪一条分支。

判定用的是真编译器,不是"用一个模式去判另一个模式"

用正则去解析正则、或手写一遍 JSON Schema 的类型检查,等于对"能不能编译"造出第二种意见,而它只会和运行时的那一份漂移 —— 那就是在上一层新开一个 declared ≠ enforced 的口子。所以:

  • regex 用 new RegExp(source) 编译 —— 就是 checkFormat 发出的那一次调用,不带 flag;
  • schema 用 ajv 编译,构造参数和运行时那个共享实例完全一致({ 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.tsLAZY_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 抹掉。
  • 理由用 issue 自己的话:坏 regex / 坏 schema 是静态的 —— 光看元数据就能判,手上不需要任何记录 —— 所以作者期这道门就能把这一类彻底关掉,且永远不会把一个跑着的部署砸掉;而在写入期拒绝,会把碰到该字段的每一次写入都拒掉,只要坏元数据还部署着。运行时是否还需要一个 backstop,留在 A format rule with an invalid regex, and a json_schema rule ajv cannot compile, still fail OPEN — the same trap #4649 closed, one rule type over #4762 上给维护者定。

另外零改动的还有:packages/spec/** 及其生成物、packages/metadata-protocol/src/protocol.tscontent/docs/releases/。分支相对 main 的 delta 就是本 PR 的 9 个文件。

验证

本地全跑,证据见下(均在合并 origin/main 之后重跑过一次):

  • pnpm --filter @objectstack/lint test57 files / 1079 tests passed
  • pnpm --filter @objectstack/lint typecheck → 干净;pnpm typecheck(全仓)→ 123 tasks successful
  • pnpm lint(ESLint 全仓)→ 通过
  • pnpm build(turbo 全仓)→ 71 tasks successful
  • pnpm --filter @objectstack/spec check:generatedAll 8 generated artifacts are up to date
  • 网关链:check: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/objectqlrule-fail-closed.test.ts → 22 tests passed

出厂样例仍然干净(非空验证 —— showcase 的 account 对象本来就带一条 format + regex 规则和一条 json_schema 规则):

pnpm --filter @objectstack/example-showcase validate   ✓ Validation passed (870ms)   exit 0
pnpm --filter @objectstack/example-crm      validate   ✓ Validation passed (260ms)   exit 0
pnpm --filter @objectstack/example-todo     validate   ✓ Validation passed (184ms)   exit 0

真 CLI 上的端到端拒绝(临时 demo stack,三个命令都 exit 1):

✗ Author-time rules failed (3 issues)
• object 'demo_account' · validation 'tax_id_format': `format` validation 'tax_id_format' on object
  'demo_account' declares a `regex` that does not compile: Invalid regular expression: /([/:
  Unterminated character class. The write path builds it with `new RegExp(rule.regex)` and SKIPS the
  rule when that throws (rule-validator.ts `checkFormat`), so the rule is declared, listed in the
  metadata, and enforces nothing on any record.
    rule: validation-rule-regex-uncompilable  at objects.demo_account.validations.tax_id_format.regex
• object 'demo_account' · validation 'support_config_shape': `json_schema` validation
  'support_config_shape' on object 'demo_account' declares a `schema` ajv cannot compile: schema is
  invalid: data/type must be equal to one of the allowed values, … The write path compiles it with
  the same ajv (`new Ajv({ allErrors: true, strict: false })`) and SKIPS the rule when that throws
  (rule-validator.ts `checkJsonSchema`), so the rule is declared and enforces nothing on any record.
    rule: validation-rule-json-schema-uncompilable  at objects.demo_account.validations.support_config_shape.schema

Changeset:.changeset/rule-compilability-publish-gate.md(@objectstack/lint patch)。


🤖 Generated with Claude Code

https://claude.ai/code/session_01NrmBxj8rK2uGCnh9aipjwX


Generated by Claude Code

claude added 3 commits August 4, 2026 00:03
…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
@vercel

vercel Bot commented Aug 4, 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 4, 2026 1:46am

Request Review

@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation dependencies Pull requests that update a dependency file tests tooling labels Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/lint.

3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/automation/hook-bodies.mdx (via @objectstack/lint)
  • content/docs/permissions/authorization.mdx (via @objectstack/lint)
  • content/docs/releases/v17.mdx (via @objectstack/lint)

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.

Copy link
Copy Markdown
Contributor Author

📌 范围外发现,已按 Prime Directive #10 单独立单,在本 PR 修复:

它是同一家族(#4649#4762)再往里一层,但方向相反 —— schema 编译得过,只是有个关键字被丢了 —— 所以本 PR 的 parity 设计(用运行时同款 ajv 参数编译)按定义抓不到它,那是那道门的正确行为。#5029 里列了三个选项(注册 ajv-formats / 在发布期拒绝 format 关键字 / 只写文档)和各自代价,留给维护者裁定。


Generated by Claude Code


Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

⚠️ CI 说明:Validate Package Dependencies 这条会红,与本 PR 无关,是 main 当前的状态。

OSV scanner 报的是 fast-uri@3.1.4 / hono@4.12.32 / hono@4.12.33 / undici@7.28.0 上共 8 条(2 High / 6 Medium)可修复告警。本 PR 的 pnpm-lock.yaml delta 只有 3 行 —— 给 packages/lint 加一条 importer 条目,指向早已被解析过ajv@8.20.0,没有新增任何 resolved package;上面四个包/版本在 origin/main 上逐个 PRESENT:

fast-uri@3.1.4     on origin/main: PRESENT
hono@4.12.32       on origin/main: PRESENT
hono@4.12.33       on origin/main: PRESENT
undici@7.28.0      on origin/main: PRESENT

已按 Prime Directive #10 立单:#5032(与 #4965 不同 —— #4965 问的是"无可用修复的告警怎么表达",这 8 条 scanner 明确写了 8 vulnerabilities can be fixed 并给出目标版本)。本 PR 不顺手修依赖。


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

Copy link
Copy Markdown
Contributor Author

➕ 追加一个提交 d82495c(自审时发现的健壮性缺口,仍在本 issue 范围内):

flattenRules 递归走 conditionalthen / otherwise 时没有深度上限。解析后的元数据是棵树,所以这不是环的问题 —— 但 os lint 从不做 parse,它把作者模块自己构造出来的那个对象原样交给这个 walker,而 const r = {…}; r.then = r 是两行就能写出来的意外。加上 MAX_RULE_NESTING_DEPTH = 16,和 flow-walk.tsMAX_REGION_DEPTH 是同一句承诺、同一条理由("a cheap promise that a hand-authored (pre-parse) stack cannot make a lint hang")。

测试是非空的:自引用规则不再挂死且返回 [];深度 16 的合法嵌套里那条坏 regex 照样被报出来;深度 17 才是 walker 停止往下看的地方。

@objectstack/lint:57 files / 1080 tests passed,typecheckpnpm lint 均干净。


Generated by Claude Code


Generated by Claude Code

@xuyushun441-sys
xuyushun441-sys marked this pull request as ready for review August 4, 2026 02:28
@xuyushun441-sys
xuyushun441-sys added this pull request to the merge queue Aug 4, 2026
Merged via the queue into main with commit da1a64c Aug 4, 2026
25 checks passed
@xuyushun441-sys
xuyushun441-sys deleted the claude/issue-4762-static-rule-publish-gate branch August 4, 2026 02:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A format rule with an invalid regex, and a json_schema rule ajv cannot compile, still fail OPEN — the same trap #4649 closed, one rule type over

2 participants