fix(docs,lint): 两处裸引用公式样例改回 canonical,并补上公式样例的 CEL 语义门 (#5116) - #5140
Merged
Conversation
…+ a CEL semantic gate (#5116) #5026 activated the field-formula check in validate-expressions.ts and its real-metadata sweep found two doc examples teaching the bare-reference form: content/docs/data-modeling/fields.mdx:230 'quantity * price * (1 - discount / 100)' content/blog/context-window-is-the-constraint.mdx:108 cel`amount * probability` A bare reference in a record-scoped CEL expression does not throw — it resolves to nothing and the expression silently evaluates to null. Both are corrected to the canonical `record.` form, verified by loading each into a minimal spec-valid stack and running the activated validateStackExpressions (RED before, GREEN after). packages/spec/src/data/field.test.ts:363 demonstrated the same wrong spelling and is corrected too; its assertion is unchanged. Adds `@objectstack/lint`'s check:doc-formula-expressions, the semantic gate that was missing: check:doc-authoring judges literal SHAPE and check:skill-examples runs tsc, so a formula that compiles and is semantically wrong passed both (`expression` is typed `string`). The verdict is validateExpression imported from @objectstack/formula — the same call `os build` makes, not a lookalike. The discriminator is the design: `expression:` carries three unrelated contracts in this corpus (record-scoped CEL, flow-flattened predicate where a bare ref is CORRECT, and a cron string), so sites are opted in only by parsed structure — `Field.*({ expression })` or `type: 'formula'` beside `expression`. A block that looks like it carries one but cannot be extracted is a hard error, not a skip. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018iARDqtrhQgz6fVHDeDkbQ
|
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:
|
…mula-example-drift
…tes (#5116) `check:nul-bytes` (#4890) caught a raw NUL at packages/lint/scripts/check-doc-formula-expressions.mjs:258 — offset 11513, well outside git's 8000-byte binary sniff, which is exactly the blind spot that gate exists for. A scan for every control byte found a second one beside it (0x01, which the NUL gate does not even look for), both in the extraction dedup key. Replaced by script with their `\u….` escape sequences — byte-equivalent at runtime, so the verdict is unchanged: the gate's self-test still passes 11/11 and the corpus scan still reports the same 22 clean examples across 375 files / 1408 TS blocks. Added a comment saying both control chars are deliberate and must stay escaped, since a raw one is invisible in review and a literal NUL makes grep/ripgrep treat the whole file as binary and silently return zero matches. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018iARDqtrhQgz6fVHDeDkbQ
xuyushun441-sys
marked this pull request as ready for review
August 4, 2026 06:43
xuyushun441-sys
enabled auto-merge
August 4, 2026 06:43
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 #5116
分两半:① 修正点名的两处裸引用样例(必做);② 评估并落地"公式样例过 CEL 语义校验"的门。
一、样例修正(实测,不是目测)
把每条样例装进最小 spec 合法 stack(先
ObjectStackSchema.safeParse,再喂validateStackExpressions,与 compile 路径完全一致),改前改后各跑一次:content/docs/data-modeling/fields.mdx:230'quantity * price * (1 - discount / 100)''record.quantity * record.price * (1 - record.discount / 100)'content/blog/context-window-is-the-constraint.mdx:108cel`amount * probability`cel`record.amount * record.probability`packages/spec/src/data/field.test.ts:363'first_name + " " + last_name''record.first_name + " " + record.last_name'改前的实测输出(六条断言全部执行):
没有加 null 保护,因为实测不需要:字段公式是
value角色、天然可空,null-guard 那条判决在validate-null-guards.ts的 surface ledger 里对Field.formula是刻意排除的,改后三条的 issue 列表都是空数组。加保护会偏离议题的处方,也偏离同页邻居 ——formulas.mdx:447的record.amount * (record.probability / 100)正是同一形状且判绿。field.test.ts的断言(FieldSchema.parse不抛)未动。blog 只改了表达式一行,没有重写文章。
二、语义门:评估结论是"便宜",所以落了
现状的洞
check:doc-authoring看字面量的形状(有没有包在defineX工厂里),check:skill-examples对os:check标记块跑tsc --noEmit。两者之间,"能编译但 CEL 写错"没有任何门 ——expression的类型就是string,'quantity * price'和'record.quantity * record.price'编译得一模一样好,而只有后者能用。这正是这两条能长期存活的原因。补一句更要命的:两处缺陷都不在标记块里,而
content/blog/根本不在check:skill-examples的 SOURCE_ROOTS 里(它只扫skills/和content/docs/)。所以哪怕把门做在标记块基上做到完美,这两条一条也抓不到。这是我把门做成独立扫描、并把content(而非content/docs)作为 root 的直接理由。为什么便宜:判决是免费的
关键测量:bare-reference 判决不需要任何对象上下文 —— 它是作用域的性质,不是字段表的性质。文档片段没有对象声明,但这不妨碍判定:
所以判决直接 import
@objectstack/formula的validateExpression('value', src, { scope: 'record' })—— 和os build/os validate/ agent 的validate_expression工具是同一个调用。这条是 PD #12:自己重写一遍"看起来像不像裸引用"会对同一个契约产生第二种意见,文档就变成被规则的方言把关,而不是被规则本身把关。真正贵的是判据 —— 这是全部设计所在
语料里同一个
expression:键承载至少三种互不相干的契约:amount * 2是Field.formula({ expression })config.conditions[].expressionschedule: { type: 'cron', expression: '0 9 * * *' }实测:40 处
expression:里 7 处是 flow 作用域、4 处是 cron。按键名匹配的门会把这 11 处全部自信地判红 —— 实测确认'order_amount > 10000'和'0 9 * * *'喂给记录作用域判决都出 error。一条判据虚的门比没有门更坏,因为它教人加 ignore。所以门只认解析后的结构(TypeScript parser,不是正则),且只认两种不可能有歧义的形状:
Field.*({ … expression … })—— 字段工厂调用;cron 在schedule下,flow 谓词在节点 config 下,都进不来。type: 'formula'与expression并列的对象字面量 —— 同一个字段的非工厂拼法。实测:在真实语料上收全了 22 条真公式样例,一条 flow/cron 都没误收。
一个实现细节值得记:文档片段常常是裸对象字面量(
{ type: 'formula', expression: '…' }),而 TS 在语句位置把开头的{解析成块语句,树里根本没有对象字面量 —— 天然假阴性,而且是静默的。恢复办法是结构性的:正常解析后,把每个顶层块语句用括号包起来重解析。这也覆盖了"先 import 再字面量"的常见文档形状(skills/objectstack-data/rules/field-types.md:296就是,门的 loudness 检查把它抓出来了,我才发现最初那版 starts-with-{的启发式不够)。覆盖面是明说的,不含糊
门不声称的部分,全部写进脚本头注释:只看 TS/TSX 代码块(YAML 样例不解析)、只看能静态取出的源(插值模板报告为不可判定而非猜)、不做字段存在性校验(片段没有对象声明)、flow / action / validation 谓词刻意不在范围内(它们的作用域取决于片段不携带的外层结构,猜就是上面那种假红)。
"看起来带公式但提取不出来"的块是硬错误,不是跳过 —— absence must be loud。
双向证明
常驻 self-test,11 例,全部执行(坏例用的是 #5116 两处缺陷的逐字原文,所以门一旦不再抓得住它本来为之而建的缺陷,self-test 就红):
外加真实语料上的改前/改后对照(不是 fixture,是把两个文件
git stash回改前状态跑的):改前 —— 门自己把这两条抓出来,定位精确到行:
改后:
三、接线(显式报告)
packages/lint/package.json— 新增check:doc-formula-expressionsscript。放packages/lint而不是根scripts/,理由有二:门需要validateExpression,而根 script 目前没有任何一个 import workspace 包(ESLint job 只pnpm install、不 build);packages/lint本来就是拥有这条判决的包,依赖方向自然,且不动根 package.json / lockfile。files白名单是["dist","README.md","CHANGELOG.md"],scripts/不会发布(check:published-files已跑绿)。.github/workflows/lint.yml— 在TypeScript Type Checkjob 里、紧挨check:skill-examples加一步。必须在 build 之后(读构建产物的 formula 包),已确认顺序:Build workspace packages(step 20)→ 本步(step 28)。packages/spec/package.json,所以check:generated的 ledger 对账不受影响(它只读 spec 的 package.json,已跑绿确认)。content/docs/releases/。验证
changeset:
@objectstack/lintpatch。Generated by Claude Code