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

xuyushun441-sys merged 5 commits into
mainfrom
claude/issue-4762-static-rule-publish-gate

Conversation

@xuyushun441-sys

Copy link
Copy Markdown
Collaborator

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:

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

每条 finding 点名规则名、对象名、配置路径,并原样带上编译器自己的错误文本 —— 作者对着 "invalid regex" 无从下手,对着 Invalid regular expression: /([/: Unterminated character class 才能改。嵌在 conditional 的 then / 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.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 抹掉。
  • 理由用 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.ts、content/docs/releases/。分支相对 main 的 delta 就是本 PR 的 9 个文件。

验证

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

  • pnpm --filter @objectstack/lint test → 57 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:generated → All 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/objectql 的 rule-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
Collaborator 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
Collaborator 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
Collaborator Author

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

flattenRules 递归走 conditional 的 then / otherwise 时没有深度上限。解析后的元数据是棵树,所以这不是环的问题 —— 但 os lint 从不做 parse,它把作者模块自己构造出来的那个对象原样交给这个 walker,而 const r = {…}; r.then = r 是两行就能写出来的意外。加上 MAX_RULE_NESTING_DEPTH = 16,和 flow-walk.ts 的 MAX_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,typecheck 与 pnpm lint 均干净。


Generated by Claude Code


Generated by Claude Code

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