#5026 把 validate-expressions.ts 的字段公式校验从 f.formula(spec 按名拒绝的别名)收敛到 f.expression,激活了一条从未跑过的检查。在做"全部真实元数据零新红"验证时顺带扫了 skills/ 和 content/ 里的公式样例 —— 三个 example app、platform-objects、default-permission-sets、skills/ 全绿,但文档和博客里有两处样例写的是裸引用,按 Prime Directive #10 单独记账,不在 #5026 的 PR 范围内(那个 PR 限于 packages/lint)。
事实
把每条样例喂给激活后的 validateExpression('value', src, { scope: 'record' }):
| 位置 |
源 |
判决 |
content/docs/data-modeling/fields.mdx:230 |
'quantity * price * (1 - discount / 100)' |
RED |
content/blog/context-window-is-the-constraint.mdx:108 |
cel`amount * probability` |
RED |
两条的错误文本一样:
bare reference `quantity` — a formula/validation expression binds the record as
the `record` namespace, not at top level, so `quantity` resolves to nothing and
the expression silently evaluates to null. Write `record.quantity`.
同一批扫描里判绿的(即 canonical、无需改动)包括 content/docs/data-modeling/formulas.mdx 的全部样例、field-types.mdx:408、getting-started/common-patterns.mdx:143、skills/objectstack-data/rules/field-types.md:300、skills/objectstack-data/rules/relationships.md:285、skills/objectstack-ui/SKILL.md:120。所以这是两处孤立的漏网,不是文档整体的惯例问题。
为什么值得修
fields.mdx 是 data-modeling 章节里讲 Field.formula() 的入口页之一,而裸引用在 CEL 里静默求值为 null——这正是 #1928 建这条检查的原因。#5026 之后,照着这一行写出来的元数据会在 os build / os lint / os validate 上判红,文档教的写法和平台的门直接矛盾。博客那篇的调性是"AI 照着 spec 写元数据",样例本身写错更尴尬。
顺带记一处不算缺陷但形状相同的:packages/spec/src/data/field.test.ts:363 的 expression: 'first_name + " " + last_name'。那个测试的被测对象是 FieldSchema.parse 能不能过,裸引用不影响它的断言,但它同样示范了错误拼法;改成 record. 前缀零成本。
修法
三处都是一行的事:
fields.mdx:230 → 'record.quantity * record.price * (1 - record.discount / 100)'
context-window-is-the-constraint.mdx:108 → cel`record.amount * record.probability`
- (可选)
field.test.ts:363 → 'record.first_name + " " + record.last_name'
content/docs/ 是手写目录(非 auto-gen),按 AGENTS.md 的 Documentation Guardrails 可以直接改;注意不要碰 content/docs/releases/。
值得一并考虑的更长期问题:check:skill-examples 只对标了 <!-- os:check --> 的块跑 tsc --noEmit,不跑任何表达式语义校验 —— 所以这类"能编译但 CEL 是错的"样例目前没有任何门。把文档/skills 里的公式样例喂给 validateExpression 做一道门,是比逐条修更根本的处置,但那是另一个单子的体量。
参考
#5026 把
validate-expressions.ts的字段公式校验从f.formula(spec 按名拒绝的别名)收敛到f.expression,激活了一条从未跑过的检查。在做"全部真实元数据零新红"验证时顺带扫了skills/和content/里的公式样例 —— 三个 example app、platform-objects、default-permission-sets、skills/全绿,但文档和博客里有两处样例写的是裸引用,按 Prime Directive #10 单独记账,不在 #5026 的 PR 范围内(那个 PR 限于packages/lint)。事实
把每条样例喂给激活后的
validateExpression('value', src, { scope: 'record' }):content/docs/data-modeling/fields.mdx:230'quantity * price * (1 - discount / 100)'content/blog/context-window-is-the-constraint.mdx:108cel`amount * probability`两条的错误文本一样:
同一批扫描里判绿的(即 canonical、无需改动)包括
content/docs/data-modeling/formulas.mdx的全部样例、field-types.mdx:408、getting-started/common-patterns.mdx:143、skills/objectstack-data/rules/field-types.md:300、skills/objectstack-data/rules/relationships.md:285、skills/objectstack-ui/SKILL.md:120。所以这是两处孤立的漏网,不是文档整体的惯例问题。为什么值得修
fields.mdx是 data-modeling 章节里讲Field.formula()的入口页之一,而裸引用在 CEL 里静默求值为 null——这正是 #1928 建这条检查的原因。#5026 之后,照着这一行写出来的元数据会在os build/os lint/os validate上判红,文档教的写法和平台的门直接矛盾。博客那篇的调性是"AI 照着 spec 写元数据",样例本身写错更尴尬。顺带记一处不算缺陷但形状相同的:
packages/spec/src/data/field.test.ts:363的expression: 'first_name + " " + last_name'。那个测试的被测对象是FieldSchema.parse能不能过,裸引用不影响它的断言,但它同样示范了错误拼法;改成record.前缀零成本。修法
三处都是一行的事:
fields.mdx:230→'record.quantity * record.price * (1 - record.discount / 100)'context-window-is-the-constraint.mdx:108→cel`record.amount * record.probability`field.test.ts:363→'record.first_name + " " + record.last_name'content/docs/是手写目录(非 auto-gen),按 AGENTS.md 的 Documentation Guardrails 可以直接改;注意不要碰content/docs/releases/。值得一并考虑的更长期问题:
check:skill-examples只对标了<!-- os:check -->的块跑tsc --noEmit,不跑任何表达式语义校验 —— 所以这类"能编译但 CEL 是错的"样例目前没有任何门。把文档/skills 里的公式样例喂给validateExpression做一道门,是比逐条修更根本的处置,但那是另一个单子的体量。参考
f.formula—— spec 声明的是expression,这段从未对任何 spec 合法 stack 跑过 #5026(激活字段公式校验;发现于其真实元数据扫描)、Formula guardrail: cel-js arithmetic silently returns null (double × int + bare identifiers) #1928(裸引用/类型健全性)、validate-expressions / validate-security-posture 也有同形的 spec 不声明键的??别名读法(#5009 建议 3 的核对结果) #5017(同族的别名收敛)