Repository navigation
Commit 40264be
docs(skills): objectstack-formula evals exercise label(record.kind), and refuse it in a predicate (#26810)
<!-- claude-projects-attribution: {"github_login":"os-steve"} -->
_Requested by **Steve** · [project
thread](https://claude.ai/code/project/chan_01FdL1XeUyvVNxLreh3F4Qi9?thread=cmsg_01FdL1XeUyvVNxLreh3F4Qi91AMradEff12Xm38NWmyrwr)_
Fixes #26760
Clause-②: no
Metadata-protocol: none
## 维护者速读
- **改了什么**:`objectstack-formula` 技能的 eval 文件新增两例 —— 正例(用 select
字段拼记录名,须写 `label(record.kind)`,禁止硬编码标签的三元)、反例(校验规则与 `visibleWhen` 里禁止出现
`label(`,它在谓词里会报错并中止写入);同文件删掉 4 句只是复述自身 `must_not_contain` 断言的话(212
字节),token 棘轮行按实测 1046 → 1352 上调,附一条 `@objectstack/skills` patch
changeset。
- **为什么改**:#26755 在 SKILL.md 教了 `label(record.f)`,但没有任何 eval 检查 agent
真会用它,也没有 eval 拦住把它写进校验谓词(校验谓词不可求值 = 每次写入都失败)。
- **风险与回滚**:只动已发布技能包的 eval 数据与棘轮上限,无运行时代码;回滚即 revert 本 PR。
-
**先看**:`skills/objectstack-formula/evals/cel-predicates-formulas.json`(case
6、7 与 case 1/2/3/5 的删句),其次 `scripts/check-skills-token-ratchet.mjs`
的新行注释(算术),最后 changeset。
- **两分钟验证**:`node scripts/check-skills-token-ratchet.mjs`,预期看到
`cel-predicates-formulas.json is 1352 tokens (ceiling 1352; headroom 0)`
与整体 ✓。
- **需要你做的**:批准本 PR(`skills/**` 为 Tier H,需 GOVERNED_APPROVERS 批准后由席位落地)。
## What changes
- **`skills/objectstack-formula/evals/cel-predicates-formulas.json`** —
two cases join the five:
- **Case 6 (positive).** Prompt: name `contract_version` records by
version number and the `kind` select so a zh-CN reader sees `v2 · 清稿`
and an en-US reader `v2 · Clean`. Expected: a `type: 'formula'`
`nameField` spelled `F`'v' + string(record.version_no) + ' · ' +
label(record.kind)``; `label()` takes exactly `record.FIELD` of a select
field, reads the option label in the reader's locale, `''` for null.
`must_contain: ["label(record.kind)", "F`"]`; `must_not_contain: ["==
'clean' ?", "{record."]` — the hard-coded label ternary the card
describes.
- **Case 7 (negative, per the seat note on the card).** Prompt: refuse a
`contract_version` save unless `kind` is the clean copy, and show
`signed_at` only once `status` is signed. Expected: predicates over the
STORED value — a `script` validation `P`record.kind != 'clean'`` (it
fails when true) and `visibleWhen: P`record.status == 'signed'``; the
`label` macro evaluates only in a formula field and faults in a
validation, `visibleWhen` or hook predicate, aborting every write.
`must_contain: ["record.kind", "visibleWhen", "P`"]`; `must_not_contain:
["label(", "{record."]`.
- **Paid down first.** Four sentences in cases 1, 2, 3 and 5 restated
their own `must_not_contain` assertions and are cut, 212 bytes = 53
units: case 1 "Field references are bare `record.FIELD`, never
brace-wrapped." (asserted by `{record.`), case 2 "Uses `'draft'` single
quotes and bare `record.` references." (`{record.`; the `'draft'`
spelling stays in the expression), case 3 "No `ISCHANGED()` function."
(`ISCHANGED(`), case 5 "Canonical property names only — never
`conditionalRequired`." (`conditionalRequired:`). Every other sentence
carries a fact (why a guard is needed, what a shape stores, which gate
refuses) and stays; every assertion stays.
- **`scripts/check-skills-token-ratchet.mjs`** — the file's row rises AT
the measurement, 1046 → 1352, headroom 0, with the arithmetic in its
comment under the standing ruling 「A:先削重复,余额按实测提高」: 1046 − 53 (the cut)
= 993; + 1438 bytes = 359 units (the two cases) = 1352.
- **`.changeset/26760-formula-label-eval.md`** — `@objectstack/skills`
patch (the package's `dist` is built from `skills/**`, so the bundle
publishes).
**Behaviour the cases pin is read off `origin/main` `8b276385`, not
invented.** `packages/formula/src/stdlib.ts` `bindOptionLabel`: `label`
is a macro taking exactly `record.FIELD`; `optionLabelOf` answers `''`
for null and THROWS when the evaluation context carries no
`optionLabels` — which is what a validation, visibility or hook
predicate's context is (only the data engine's formula-field reads pin
the map, per the #26755 changeset), so the engine reports `{ ok: false,
kind: 'runtime' }` and the write is aborted.
`packages/formula/src/option-label.test.ts` pins both ("refuses at run
time when the context carries no option labels — never the stored
value"; "compiles the one argument shape and refuses every other").
`content/docs/data-modeling/formulas.mdx` § "Record name from an option
label" says the same for predicates: "fails loudly instead of comparing
against a label; compare the stored value there".
### Readings (`skills/**` touched)
| Reading | Before | After |
|:--|--:|--:|
| `evals/cel-predicates-formulas.json` lines | 55 | 75 |
| `evals/cel-predicates-formulas.json` tokens (`ceil(utf8 bytes / 4)`) |
1046 (4181 B) | 1352 (5407 B) |
| whole package, sum of all `skills/*/SKILL.md` lines | 4606 | 4606 |
| whole package, sum of all `skills/*/SKILL.md` tokens | 56132 (224526
B) | 56132 (224526 B) |
| ratchet row for the file | 1046 | 1352 (headroom 0) |
No `SKILL.md` moves; the net addition is +306 units in one eval file,
paid first by the 53-unit cut and then raised at the measurement.
## Tests
No runtime code changes. The eval file is read by no runner in this
repository; `scripts/check-skills-token-ratchet.mjs` is its only reader
and is in the gate list below. JSON parses (`JSON.parse` over the file
at head, 7 cases); every `must_contain` string is absent from the same
case's `must_not_contain` and present in its `expected_output`; in cases
6 and 7 no `must_not_contain` string appears in the `expected_output`.
## Acceptance notes
- The card's "hard-coded label ternary" is pinned as the literal `==
'clean' ?` (the stored value the prompt names, compared and branched
on); a ternary over another spelling would pass `must_not_contain`, and
the `must_contain` of `label(record.kind)` is what carries the teaching.
- Case 7's expected output spells the macro as "the `label` macro"
rather than `label()` so the ideal answer itself does not carry the
string the case refuses (the same contradiction case 3 had on `main`,
`ISCHANGED()` inside its own refusal, is gone at this head: the
paid-down cut removed that sentence).
- `check:skill-docs` / `check:skill-refs` / `check:skill-examples` read
`SKILL.md`, `references/` and rule code fences, not eval JSON; the
derived gate list below does not name them, consistent with that.
## Verification
Gate list derived at head with `node scripts/pm/dispatch-gates.mjs
--commands --repo objectstack-ai/objectstack` (60 commands; the model
tier line reads MANDATORY, `skills/**` clause ①, no one-line exit), each
run with stdin closed, reconciled with `--ran`. Readings follow in the
os-dev-report comment on the card.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01ADHkmPm7NR24Xypi61eyg8)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent bdb00ff commit 40264be
3 files changed
Lines changed: 48 additions & 5 deletions
File tree
- .changeset
- scripts
- skills/objectstack-formula/evals
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
882 | 882 | | |
883 | 883 | | |
884 | 884 | | |
885 | | - | |
| 885 | + | |
| 886 | + | |
| 887 | + | |
| 888 | + | |
| 889 | + | |
| 890 | + | |
| 891 | + | |
| 892 | + | |
| 893 | + | |
| 894 | + | |
| 895 | + | |
| 896 | + | |
886 | 897 | | |
887 | 898 | | |
888 | 899 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
| 7 | + | |
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
17 | | - | |
| 17 | + | |
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
| |||
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
27 | | - | |
| 27 | + | |
28 | 28 | | |
29 | 29 | | |
30 | 30 | | |
| |||
44 | 44 | | |
45 | 45 | | |
46 | 46 | | |
47 | | - | |
| 47 | + | |
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
51 | 51 | | |
52 | 52 | | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
53 | 73 | | |
54 | 74 | | |
55 | 75 | | |
0 commit comments