Skip to content

fix(lint): 字段公式校验读回声明的 expression —— 激活一条从未跑过的检查 (#5026) - #5118

Merged
xuyushun441-sys merged 2 commits into
mainfrom
claude/issue-5026-field-formula-activate
Aug 4, 2026
Merged

fix(lint): 字段公式校验读回声明的 expression —— 激活一条从未跑过的检查 (#5026)#5118
xuyushun441-sys merged 2 commits into
mainfrom
claude/issue-5026-field-formula-activate

Conversation

@xuyushun441-sys

Copy link
Copy Markdown
Contributor

Fixes #5026

这一单和 #4984 / #5009 / #5017 的差别

同族的前三轮都是删死代码:别名 limb 在链里,canonical limb 也在,删掉别名对任何能解析的 stack 零行为变化。这一条不是。

validate-expressions.ts 的字段公式校验读 f.formula,而 FieldSchema 声明的是 expression —— formula 恰恰是 field.zod.ts:333 按名拒绝的别名:

aliases: { formula: 'expression', calculation: 'expression', compute: 'expression' }

规则以 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-showcase 3 0
examples/app-crm 5 0
examples/app-todo 0 0
packages/platform-objects 0(无公式字段) 0
plugin-security default-permission-sets 0(无公式字段) 0
skills/ 公式样例(3 处) 3 0(已是 canonical)

八个真实公式槽全部判绿。这不是"门没开"——反向验证里坏 expression 确实判红(见下),所以绿是判别的结果,不是不可达的结果。

顺带扫到的两处裸引用样例在文档/博客里(content/docs/data-modeling/fields.mdx:230'quantity * price * …'content/blog/context-window-is-the-constraint.mdx:108cel`amount * probability`),两条都实测判红。它们是 markdown 散文、不在本规则的消费半径内,按 Prime Directive #10 另开 #5116 记账,没有在本 PR 里改(本 PR 限于 packages/lint)。

双向证明

新增 validateStackExpressions — the field-formula check now actually runs (#5026),四条,全部执行而非描述:

  1. spec 合法 stack 上坏 expression 判红且点名 —— unknown field 'no_such_field',定位到 field 'expected_revenue' expression;1b 语法坏的同样判红;
  2. 正确的 expression 判绿 —— 门会判别,不是一律亮;
  3. formula: 拼法交给 schema 按名拒绝(Did you mean 'formula' → 'expression'?),本规则不再对同一个键给第二套说法;
  4. 变异:把源码里的读法改回 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:

其他改动

  • 诊断定位串 … field 'Y' formula… field 'Y' expression。理由写在代码注释里:说 "formula" 正是错拼法的传播路径 —— 下一位作者读了诊断就去写 formula:,然后被 schema 拒。消息要点名作者要改的那个键
  • validate-null-guards.ts 的 surface ledger 把该行从 Field.formula 正名为 field expression(Field.formula({ expression: … }) 写入的槽),并写明:排除的只是 null-guard 这一条判决(公式是 value 角色、天然可空,guard ? value : null 是祝福写法),语法 / 字段存在性 / 裸引用判决从此生效。对应的那条测试也补了注释 —— 它现在零 issue 是"gate 确实被排除",而不是过去的"surface 压根不可达"。
  • changeset:@objectstack/lint minor。

验证

pnpm --filter @objectstack/lint test       →  57 files / 1169 tests passed
pnpm --filter @objectstack/lint typecheck  →  tsc --noEmit, clean
eslint (三个改动文件)                       →  exit 0
pnpm --filter @objectstack/cli test        →  68 files / 600 tests passed

已 merge origin/main(5 个新 commit,均不触及 packages/{lint,spec,formula},无冲突),在合并后的树上重跑 lint 测试 + typecheck 仍全绿。


Generated by Claude Code

claude added 2 commits August 4, 2026 05:21
…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
@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 5:23am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/m 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

validate-expressions.ts 的 field-formula 校验读 f.formula —— spec 声明的是 expression,这段从未对任何 spec 合法 stack 跑过

2 participants