Skip to content

Commit 827f935

Browse files
committed
docs(skills): objectstack-formula evals exercise label(record.kind), and refuse it in a predicate
Two eval cases join cel-predicates-formulas.json: a record name built from a select field expects label(record.kind) and refuses a hard-coded label ternary; a validation rule and a visibleWhen refuse label( where it faults. Four sentences that restated their own must_not_contain assertions are cut (212 bytes); the ratchet row rises to the measurement, 1046 -> 1352, under the ruling 「A:先削重复,余额按实测提高」. Claude-Session: https://claude.ai/code/session_01ADHkmPm7NR24Xypi61eyg8 Co-authored-by: Claude <noreply@anthropic.com>
1 parent 8b27638 commit 827f935

3 files changed

Lines changed: 48 additions & 5 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@objectstack/skills': patch
3+
---
4+
5+
docs(skills): objectstack-formula evals check that a record name built from a select field reaches for `label(record.kind)`, and that a predicate never calls `label()`
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- **Before.** `skills/objectstack-formula/SKILL.md` teaches `label(record.f)`, but no eval in `evals/cel-predicates-formulas.json` exercised it: an agent authoring a record name from a select field could hand-roll a ternary over the stored value with hard-coded English or Chinese strings, and nothing caught it; nor did anything catch `label()` written into a validation rule, where it faults and aborts every write.
11+
- **Now.** Eval 6 asks for a `contract_version` name built from its `kind` select and expects `label(record.kind)` in an `F` formula, refusing a `== 'clean' ?` ternary. Eval 7 asks for a validation rule and a `visibleWhen` on the same object and refuses `label(` in either, expecting predicates over the stored value. Four sentences in evals 1, 2, 3 and 5 that restated their own `must_not_contain` assertions are gone; every fact and assertion stays.
12+
- No schema, key or behaviour changes.

‎scripts/check-skills-token-ratchet.mjs‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -863,7 +863,18 @@ export const CEILINGS = new Map([
863863
// Maintainer ruling 2026-10-27 on the decision card 「skills 修复卡的教学和 eval 都撞 token 天
864864
// 花板,怎么付?」, tap A, verbatim and untranslated: 「A:先削重复,余额按实测提高」.
865865
['skills/objectstack-data/evals/objects-fields-relationships.json', 1050],
866-
['skills/objectstack-formula/evals/cel-predicates-formulas.json', 1046],
866+
// 1046 -> 1352 (card #26760): two eval cases for the `label(record.FIELD)`
867+
// teaching — case 6 asks for a record name built from a select field and
868+
// expects `label(record.kind)`, never a hard-coded label ternary; case 7
869+
// asks for a validation and a `visibleWhen` and refuses `label(` there,
870+
// where it faults and aborts the write. Maintainer ruling 2026-10-27 on the
871+
// decision card 「skills 修复卡的教学和 eval 都撞 token 天花板,怎么付?」, tap A,
872+
// verbatim and untranslated: 「A:先削重复,余额按实测提高」. PAID DOWN FIRST:
873+
// four sentences in cases 1, 2, 3 and 5 that restated their own
874+
// `must_not_contain` assertions, 212 bytes = 53 units (1046 -> 993); the two
875+
// cases add 1438 bytes = 359 units (993 -> 1352); pinned AT the measurement,
876+
// zero headroom. The raising PR's body carries the same arithmetic.
877+
['skills/objectstack-formula/evals/cel-predicates-formulas.json', 1352],
867878
// 1004 -> 1451 (2026-10-27, the objectstack-i18n fix card of the skills gap
868879
// audit): two eval prompts for the teaching that card adds — the `reports`,
869880
// `positions`, `permissions` and `picklists` groups; a form view `title`, a

‎skills/objectstack-formula/evals/cel-predicates-formulas.json‎

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
{
55
"id": 1,
66
"prompt": "Add a `full_name` formula on `contact` built from salutation, first name and last name, and a `margin_pct` formula on `deal` = (revenue - cost) / cost * 100.",
7-
"expected_output": "Two `type: 'formula'` fields whose `expression` is CEL via the `F` tag. The text formula coalesces every nullable operand — `F`coalesce(record.salutation, '') + ' ' + coalesce(record.first_name, '') + ' ' + coalesce(record.last_name, '')`` — because `null + 'foo'` throws. The numeric one guards the divisor: `F`coalesce(record.cost, 0) > 0 ? ((coalesce(record.revenue, 0) - record.cost) * 100) / record.cost : 0.0``. Field references are bare `record.<field>`, never brace-wrapped.",
7+
"expected_output": "Two `type: 'formula'` fields whose `expression` is CEL via the `F` tag. The text formula coalesces every nullable operand — `F`coalesce(record.salutation, '') + ' ' + coalesce(record.first_name, '') + ' ' + coalesce(record.last_name, '')`` — because `null + 'foo'` throws. The numeric one guards the divisor: `F`coalesce(record.cost, 0) > 0 ? ((coalesce(record.revenue, 0) - record.cost) * 100) / record.cost : 0.0``.",
88
"files": [],
99
"assertions": {
1010
"must_contain": ["F`", "coalesce(", "record."],
@@ -14,7 +14,7 @@
1414
{
1515
"id": 2,
1616
"prompt": "Translate this Salesforce validation rule into ObjectStack: AND(ISBLANK(po_number), amount > 10000, NOT(ISPICKVAL(status, 'draft'))).",
17-
"expected_output": "Applies the mechanical legacy → CEL table: `P`isBlank(record.po_number) && record.amount > 10000 && record.status != 'draft'``, noting that a `script` validation is inverted (it FAILS when the expression is true, so keep the failing condition) and that `record.amount > 10000` on a declared-nullable field needs an explicit `record.amount != null &&` guard or the publish gate rejects it. Uses `'draft'` single quotes and bare `record.` references.",
17+
"expected_output": "Applies the mechanical legacy → CEL table: `P`isBlank(record.po_number) && record.amount > 10000 && record.status != 'draft'``, noting that a `script` validation is inverted (it FAILS when the expression is true, so keep the failing condition) and that `record.amount > 10000` on a declared-nullable field needs an explicit `record.amount != null &&` guard or the publish gate rejects it.",
1818
"files": [],
1919
"assertions": {
2020
"must_contain": ["isBlank(", "&&", "record.", "P`"],
@@ -24,7 +24,7 @@
2424
{
2525
"id": 3,
2626
"prompt": "Run an automation only when a support case has just become `escalated` — on single updates and on bulk updates alike.",
27-
"expected_output": "Writes it once as `P`previous.status != 'escalated' && record.status == 'escalated'``: `record` is the post-write state (true on every later update too), so only `previous` says \"just became\"; `previous` is bound per row on `before*`/`after*` update events, bulk too. On insert `previous` is `null`: the `previous.status` read faults and aborts the write, so to run on inserts guard with `previous != null &&`. No `ISCHANGED()` function.",
27+
"expected_output": "Writes it once as `P`previous.status != 'escalated' && record.status == 'escalated'``: `record` is the post-write state (true on every later update too), so only `previous` says \"just became\"; `previous` is bound per row on `before*`/`after*` update events, bulk too. On insert `previous` is `null`: the `previous.status` read faults and aborts the write, so to run on inserts guard with `previous != null &&`.",
2828
"files": [],
2929
"assertions": {
3030
"must_contain": ["previous.status", "record.status", "!="],
@@ -44,12 +44,32 @@
4444
{
4545
"id": 5,
4646
"prompt": "Hide `discount_reason` unless a discount amount was entered, and reject any `end_date` earlier than `start_date`.",
47-
"expected_output": "`visibleWhen: P`record.discount != null`` (or `!isBlank(record.discount)`) on the field — `has(record.discount)` is not a null check: every declared field exists, so it is always true. The date rule is a `script` validation whose predicate dominates the comparison with explicit null tests: `record.start_date != null && record.end_date != null && record.end_date < record.start_date`; a `has()` guard does not satisfy the publish-time gate. Canonical property names only — never `conditionalRequired`.",
47+
"expected_output": "`visibleWhen: P`record.discount != null`` (or `!isBlank(record.discount)`) on the field — `has(record.discount)` is not a null check: every declared field exists, so it is always true. The date rule is a `script` validation whose predicate dominates the comparison with explicit null tests: `record.start_date != null && record.end_date != null && record.end_date < record.start_date`; a `has()` guard does not satisfy the publish-time gate.",
4848
"files": [],
4949
"assertions": {
5050
"must_contain": ["visibleWhen", "!= null", "record.end_date"],
5151
"must_not_contain": ["conditionalRequired:", "{record."]
5252
}
53+
},
54+
{
55+
"id": 6,
56+
"prompt": "Name `contract_version` records by their version number and the `kind` select, so a zh-CN reader sees `v2 · 清稿` and an en-US reader `v2 · Clean`.",
57+
"expected_output": "A `type: 'formula'` `nameField`: `F`'v' + string(record.version_no) + ' · ' + label(record.kind)``. `label()` takes exactly `record.<field>` of a select field and reads its option label in the reader's locale (`''` for null). The stored value is the same in every locale, so a ternary over it with hard-coded strings cannot follow the reader.",
58+
"files": [],
59+
"assertions": {
60+
"must_contain": ["label(record.kind)", "F`"],
61+
"must_not_contain": ["== 'clean' ?", "{record."]
62+
}
63+
},
64+
{
65+
"id": 7,
66+
"prompt": "Refuse a `contract_version` save unless its `kind` is the clean copy, and show `signed_at` only once `status` is signed.",
67+
"expected_output": "Both are 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; in a validation, `visibleWhen` or hook predicate it faults and aborts every write, so compare the stored value, never its option label.",
68+
"files": [],
69+
"assertions": {
70+
"must_contain": ["record.kind", "visibleWhen", "P`"],
71+
"must_not_contain": ["label(", "{record."]
72+
}
5373
}
5474
]
5575
}

0 commit comments

Comments
 (0)