fix(lint): 字段公式校验读回声明的 expression —— 激活一条从未跑过的检查 (#5026) - #5118
Merged
Conversation
…ating a pass that never ran (#5026) `validate-expressions.ts`'s field-formula pass read `f.formula`. `FieldSchema` declares the computed slot as `expression`; `formula` is one of the names `field.zod.ts:333` rejects BY NAME (`aliases: { formula: 'expression', … }`). The rule is registered `input: 'parsed'`, so on the compile path it sees `ObjectStackSchema`'s output, where `f.formula` is always `undefined` — the whole branch had never executed against a stack an author can ship. Converging the read onto `expression` therefore ACTIVATES a check rather than deleting a dead branch: field formulas now carry the ADR-0032 §1a/1b verdicts (CEL syntax, `record.<field>` existence, the #1928 bare-reference and type-soundness tiers). Bare refs in a formula silently evaluate to null, and they are the single most common defect in AI-authored formula slots, which is what #1928 built this check for. Coverage widened, zero new findings on real metadata. Swept every stack in the repo with the activated check: examples/app-showcase (3 `expression` slots), examples/app-crm (5), examples/app-todo (0) all parse and stay green; platform-objects and plugin-security's default-permission-sets declare no formula fields; the `skills/` samples are already canonical. Two bare-ref samples in `content/docs` + `content/blog` are out of this rule's consumer radius and filed as #5116 rather than fixed here. - `TRACKED_UNDECLARED_READS` in the meta-guard drops to EMPTY — every key the rule reads is now one the spec declares. - Diagnostic locator renamed `… field 'Y' formula` → `… field 'Y' expression`, so the message names the key the author edits instead of propagating the spelling the schema refuses. - The 7 field-predicate fixtures spelled `formula:` move to `expression:`; a `fields[].formula → expression` case joins the rejected-alias table (schema refuses by name, this rule stays silent) and a reachability case proves the pass fires from a stack that really parses. - New reverse verification: broken `expression` red, correct one green, `formula:` left to the schema, and the read mutated back to `f.formula` caught by the declared-key guard — executed, not asserted. - `validate-null-guards.ts`'s surface ledger renames the row to field `expression` and states that only the NULL-GUARD verdict stays excluded. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018iARDqtrhQgz6fVHDeDkbQ
…ld-formula-activate
|
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:
|
xuyushun441-sys
marked this pull request as ready for review
August 4, 2026 05:33
This was referenced Aug 4, 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 #5026
这一单和 #4984 / #5009 / #5017 的差别
同族的前三轮都是删死代码:别名 limb 在链里,canonical limb 也在,删掉别名对任何能解析的 stack 零行为变化。这一条不是。
validate-expressions.ts的字段公式校验读f.formula,而FieldSchema声明的是expression——formula恰恰是field.zod.ts:333按名拒绝的别名:规则以
input: 'parsed'注册(authoring-rules.ts),compile/build/validate 路径上它看到的是ObjectStackSchema的解析产物,所以f.formula恒为undefined。代码里根本没有f.expression的读法。结论:整段检查对任何 spec 合法 stack 从未执行过一次。所以把读法收敛为
f.expression是启用一条从未跑过的检查,是覆盖面扩大 —— 不是删死代码。这也是它没跟着 #5017 顺手带上、而是单独一单的原因。真实元数据实测:零新红
激活是覆盖面扩大,可能对现有元数据判红 —— 那是本单的目的而非事故,所以逐条跑过、逐条定性。用激活后的规则在全部真实元数据上实测(先
ObjectStackSchema.safeParse,再喂validateStackExpressions,和 compile 路径完全一致):expression槽examples/app-showcaseexamples/app-crmexamples/app-todopackages/platform-objectsplugin-securitydefault-permission-setsskills/公式样例(3 处)八个真实公式槽全部判绿。这不是"门没开"——反向验证里坏
expression确实判红(见下),所以绿是判别的结果,不是不可达的结果。顺带扫到的两处裸引用样例在文档/博客里(
content/docs/data-modeling/fields.mdx:230的'quantity * price * …'、content/blog/context-window-is-the-constraint.mdx:108的cel`amount * probability`),两条都实测判红。它们是 markdown 散文、不在本规则的消费半径内,按 Prime Directive #10 另开 #5116 记账,没有在本 PR 里改(本 PR 限于packages/lint)。双向证明
新增
validateStackExpressions — the field-formula check now actually runs (#5026),四条,全部执行而非描述:expression判红且点名 ——unknown field 'no_such_field',定位到field 'expected_revenue' expression;1b 语法坏的同样判红;expression判绿 —— 门会判别,不是一律亮;formula:拼法交给 schema 按名拒绝(Did you mean 'formula' → 'expression'?),本规则不再对同一个键给第二套说法;f.formula,fix(lint): 收敛 validate-expressions / validate-security-posture 的 spec 不声明键??别名读法 (#5017) #5046 的 declared-key meta-guard 用同一段 source scan 判红并点名formula—— 接住了,已验证。另有 reachability 一条:
fields[].expression的判决从一个真正ObjectStackSchema.parse过的 stack 上产生 —— 这句话在本 PR 之前从来不成立。TRACKED_UNDECLARED_READS清账那份"只缩不长"的清单唯一一条就是
{ receiver: 'f', key: 'formula', issue: 5026 }。本单清空到 0:规则读的每一个键都是 spec 声明的键。断言从"恰好等于这一条 debt"改成toEqual([]),再加一条独立断言把"清单为空"钉住,任何新增条目都成为一次刻意行为。消费半径扫描(#5046 返工的教训)
全仓 grep
formula:作字段谓词键的测试 fixture:packages/lint/src/validate-expressions.test.ts7 处 → 全部换 canonicalexpression:(185 / 214 / 234 / 255 / 329 / 348 / 1171);formula → condition别名 fixture,是刻意判红的对象,不属本单面,保留;packages/cli/test/authoring-rule-command-parity.test.ts的expression-invalid用例写的是condition:(validate-expressions / validate-security-posture 也有同形的 spec 不声明键的??别名读法(#5009 建议 3 的核对结果) #5017 已修过),不涉字段公式,无需改;cli 全量测试已跑绿(68 files / 600 tests);#5096playbook 点名的validate-rule-compilability.test.ts:295—— 该文件grep formula零命中,与本单面不重叠,留给 validate-rule-compilability.ts:239 也有同形的obj.validations ?? obj.validationRules别名读法(#5017 全包 grep 的第八处,第三个文件) #5096;driver-sql/driver-sqlite-wasm/objectql等包里的expressionfixture 是 DDL / 引擎测试,不经过本规则,不在半径内。其他改动
… field 'Y' formula→… field 'Y' expression。理由写在代码注释里:说 "formula" 正是错拼法的传播路径 —— 下一位作者读了诊断就去写formula:,然后被 schema 拒。消息要点名作者要改的那个键。validate-null-guards.ts的 surface ledger 把该行从Field.formula正名为 fieldexpression(Field.formula({ expression: … })写入的槽),并写明:排除的只是 null-guard 这一条判决(公式是value角色、天然可空,guard ? value : null是祝福写法),语法 / 字段存在性 / 裸引用判决从此生效。对应的那条测试也补了注释 —— 它现在零 issue 是"gate 确实被排除",而不是过去的"surface 压根不可达"。@objectstack/lintminor。验证
已 merge
origin/main(5 个新 commit,均不触及packages/{lint,spec,formula},无冲突),在合并后的树上重跑 lint 测试 + typecheck 仍全绿。Generated by Claude Code