Skip to content

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

Closed

Description

@xuyushun441-sys

#5017 清理这两个文件里的 ?? 别名链时顺带发现的,形状同族但不是 ?? 链,所以按 Prime Directive #10 单独记账,不在 #5017 的 PR 范围内。

事实

packages/lint/src/validate-expressions.ts 的字段公式校验(#5017 分支上约 570-590 行,if (f.formula) { … } 整段)读的是 f.formula:

if (f.formula) {
  const res = validateExpression('value', f.formula as ..., { objectName, fields, fieldTypes, scope: 'record' });
  // → `object 'X' · field 'Y' formula` 的 error / warning
}

FieldSchema.shape 没有 formula 键。canonical 是 expression,而 formula 正是 field.zod.ts:333 里按名拒绝的别名:

formula: 'expression', calculation: 'expression', compute: 'expression',

实测(worktree 上 ObjectStackSchema.safeParse):

object w/ field.formula parses? false
  ["objects.0.fields.a: Unrecognized key(s) on this field: `formula`. …
    Did you mean `formula` → `expression`?"]
object w/ field.expression parses? true
  {"type":"formula","expression":{"dialect":"cel","source":"record.x + 1"},"returnType":"number", …}

该规则以 input: 'parsed' 注册(authoring-rules.ts),所以在 compile/build/validate 路径上看到的是解析产物 —— f.formula 恒为 undefined。

和 #5017 那五条的差别:这条不是"收敛为 canonical",是"canonical 从来没被读过"

#5017 的每一条都有 canonical limb 在链里,删掉别名 limb 对任何能解析的 stack 零行为变化。这条不同:代码里根本没有 f.expression 的读法。所以

  • 现状:字段公式校验对任何 spec 合法 stack 都是完全不执行的;
  • 改成读 f.expression:等于启用一条从未跑过的检查,是覆盖面扩大,可能对现有真实元数据判红。

真实元数据确实全部用 canonical 拼法,即这段今天一条都没覆盖到:

位置 拼法
examples/app-showcase/src/data/objects/project.object.ts:67 expression: cel\(record.budget == null ? 0 : record.budget) - …``
examples/app-showcase/src/data/objects/invoice.object.ts:243 expression: cel\record.quantity * record.unit_price``
examples/app-showcase/src/data/objects/field-zoo.object.ts:132 expression: cel\…``
examples/app-crm/src/objects/{lead,opportunity,contact}.object.ts Field.formula({ expression: … })

validate-expressions.test.ts 里现存的 7 处 formula: fixture({ type: 'formula', formula: '…' })也都不是 spec 合法的 —— 这段分支今天全部的覆盖来自不能解析的 fixture。

需要决定的(所以没顺手改)

  1. 直接把 f.formula 换成 f.expression:恢复本意的覆盖,但要先验证真实元数据零新红(value 角色 + record scope,Formula guardrail: cel-js arithmetic silently returns null (double × int + bare identifiers) #1928 裸引用告警会一起生效),并把 7 处 fixture 改成 canonical 拼法。
  2. 删掉整段:与 validateOrgAxisRedLines 仍留着三条 spec 合法 stack 到不了的别名分支(objects[].rowLevelSecurity / .rls / permissionSets)—— #4984 修了 sharing 那一半 #5009 删 object-level RLS 同款处置。但这里和那里不同 —— 字段公式是真实存在的授权/计算面,只是键名写错了,删掉会丢掉一条本应存在的 gate。
  3. 顺带要一并决定的:validate-null-guards.ts 里 Field.formula 被明确列为 excluded surface(理由是 value 角色天然可空、guard ? value : null 是祝福写法)。那条 ledger 说的是"不给 null-guard gate",不是"不给语法/字段存在性校验" —— 改动 1 不影响它,但注释里的 surface 名要跟着从 formula 改成 expression。

倾向 1,理由两条轴:

但 1 是覆盖面扩大、可能对现有真实元数据判红,不该由一个"删死代码"的 PR 顺手带上,所以在这里等 maintainer 拍板。

参考

Activity

  1. xuyushun441-sys commented on Aug 4, 2026

    @xuyushun441-sys
    CollaboratorAuthor

    认领(PM 分派)

    注意本单性质:启用一条从未跑过的检查,不是删死代码 —— 可能对现有元数据判出真红,逐条核实定性,不放宽。


    Generated by Claude Code

  2. xuyushun441-sys commented on Aug 4, 2026

    @xuyushun441-sys
    CollaboratorAuthor

    审核通过 — PR #5118 已取消草稿并挂上 auto-merge(SQUASH);沉睡的检查激活,别名家族的记账清零

    "零新红"是判别结果,不是门没开 —— 这句话有证据支撑:反向验证 1/1b 证明坏 expression(未知字段 / 语法错)确实判红并点名定位,第 2 条证明好的判绿;八个真实公式槽全绿是门会判别的结论。这正是激活型任务与清死代码型任务在验收上的区别,dev 把它讲清了。

    诊断定位串的改名超出议题字面,但理由成立且我认可:field 'Y' formula 留着就是错拼法的传播路径 —— 下一位作者(人或 AI)照着诊断去写 formula: 再被 schema 拒。全仓核实无消费者断言旧串,回退成本一行。这与 #4992 的 expressionText 不读 meta.rationale 同属一类判断:诊断文本本身是语料,会教人。

    fixture 扫尾 7 处 re-spell + 三类"核查过不需改"逐条列明(含与在飞 #5096 的边界确认);TRACKED_UNDECLARED_READS 从一条到空表,且空表本身被断言钉住(只缩不长的 ratchet 走到了终点);变异验证确认 #5046 的 declared-key guard 真能接住回归(undeclared 恰为 ['formula'])。

    衍生 #5116 的两层价值:表层是文档/博客里两条裸引用公式样例(实测判红,在激活后的规则下是真错);深层是它揭示的缺口 —— check:skill-examples 只跑 tsc,"能编译但 CEL 写错"的样例目前没有任何门,这正是那两条能长期存活的原因。已标 bug 入队。

    CI 23 项无红;lint 1169 + cli 600 全绿。落点纯 packages/lint。


    Generated by Claude Code

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

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions