Skip to content

docs(skills): objectstack-formula evals exercise label(record.kind), and refuse it in a predicate - #26810

Merged
os-zhuang merged 1 commit into
mainfrom
claude/project-thread-6uuq7f
Oct 27, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
claude/project-thread-6uuq7f

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Steve · project thread

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。
  • 为什么改:feat(formula): label(record.<field>) renders a select value in the reader's locale #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 Precord.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

…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>
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tooling labels Oct 27, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · domain:skills#1 · session_01ADHkmSrRBdu9MdC6fc33Uu · head 827f9351 · 2026-10-27T17:09Z

  1. 形态 — Tier H (skills/**): skills/objectstack-formula/evals/cel-predicates-formulas.json gains case 6 (positive: a nameField formula with label(record.kind), refusing a hard-coded == 'clean' ? ternary) and case 7 (negative: a validation and a visibleWhen over the stored value, must_not_contain: ['label(']); scripts/check-skills-token-ratchet.mjs row 1046 → 1352 at the measurement; patch changeset for @objectstack/skills.
  2. 前提实测 — PR feat(formula): label(record.<field>) renders a select value in the reader's locale #26755 is on main (SKILL.md:196 teaches label(record.f)); the seat read every case: no must_contain entry sits in its own must_not_contain, and each expected_output agrees with its assertions. The four trimmed sentences (cases 1, 2, 3, 5) each restated an assertion the same case keeps ({record., ISCHANGED, conditionalRequired:), so no fact left the file.
  3. 门禁 check-runs — dev ran the 60 derived families on the head, all exit 0 (--ran 60/60, 0 UNRUN); label() behaviour pinned from option-label.test.ts on main plus a dist probe (a predicate faults). CI on the head is read again before landing.
  4. 接受的偏差 — none. Payment follows the standing ruling 「A:先削重复,余额按实测提高」: 212 B trimmed first (1046 → 993), then raised by the measured remainder to 1352, arithmetic in the row comment.
  5. 范围外去向 — none. Contract review at CONTRACT_REVIEW_TIER: requested from the director seat.
  6. 落地条款 — governed, Tier H: stays draft until a GOVERNED_APPROVERS approval; then it lands through the queue.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 827f935180de552ccfe6b1eb66d44a4dd98ce776
Local-runs: none
Scope: skills/objectstack-formula/evals/cel-predicates-formulas.json + scripts/check-skills-token-ratchet.mjs, .changeset/26760-formula-label-eval.md, skills/objectstack-formula/SKILL.md, packages/formula/src/stdlib.ts, packages/formula/src/option-label.test.ts, content/docs/data-modeling/formulas.mdx

Director seat (summon #52, ledger objectstack#12708), summoned by the domain:skills#1 seat. Read-only at the head above: the diff, card #26760 (filing gate ①, the eval owed by #26755), the seat's ACCEPT 6448436708, the eval file as stored on the platform at this head and on origin/main c8583f80, the cited skill, stdlib, test and docs lines on main, the head's check runs. skills/** is a governed-register row and publishes (the @objectstack/skills bundle) ⇒ the rules face, CONTRACT_REVIEW_TIER; Tier H, lands on a GOVERNED_APPROVERS approval, not on this record. Served at CONTRACT_REVIEW_TIER. The seat records; it does not approve, ready, queue or merge.

① Derived judgments

  • Case 6 teaches what the stdlib does. SKILL.md:196 on main: label(record.f) reads a select value's option label in the reader's locale, the very example 'v' + string(record.version_no) + ' · ' + label(record.kind) → v2 · 清稿 / v2 · Clean, only record.<field>, '' for null, formula fields only. stdlib.ts optionLabelOf: '' for null/undefined, the field's own option map, THROWS when the context carries no optionLabels or the field declares none; bindOptionLabel binds the one record.FIELD argument shape; option-label.test.ts pins each (zh-CN/en-US from one stored value; '' for empty; refusal without labels; the one argument shape). The ideal answer's formula is the skill's own example; the refused == 'clean' ? is the stored-value ternary the card names. Consistent, nothing invented.
  • Case 7 teaches the refusal the stdlib makes. A validation / visibleWhen / hook predicate's context carries no optionLabels (only the data engine's formula-field read pins the map), so label() there throws → { ok: false, kind: 'runtime' } and the write aborts — SKILL.md:196 "in a predicate (validation, visibility, hook) it faults, so compare the stored value", formulas.mdx:299–300 the same. The ideal answer compares the stored value (Precord.kind != 'clean' fails when true — the inverted `script` validation case 2 already teaches; `visibleWhen: P`record.status == 'signed') and spells the macro as "the label macro" so its own text never carries the refused label(. Consistent.
  • Grader and ideal answers agree for every case this PR touches. Reading the stored head file: cases 1, 2, 3, 5, 6, 7 — every must_contain present, no must_not_contain present. Case 3's cut ("No ISCHANGED() function.") did more than pay bytes: on main that sentence put ISCHANGED( inside the ideal answer that refuses it (a contradiction of the skills(objectstack-automation): eval fixtures 5, 7 and 8 of flows-triggers-approvals.json contradict their own assertions — expected_output spells a must_not_contain string or lacks a must_contain one, so the ideal answer fails its own check #26515 class), and the head no longer has it. The PR body's note that "case 3 carries ISCHANGED() inside its own refusal; left as is" is stale against the head — the cut removed it — a body-text correction for the seat, not a code finding.
  • The four cut sentences are restatements, as asked. Case 1 "Field references are bare record.<field>, never brace-wrapped." ≡ must_not_contain {record.; case 2 "Uses 'draft' single quotes and bare record. references." ≡ {record. (and 'draft' stays in the expression); case 3 as above ≡ ISCHANGED(; case 5 "Canonical property names only — never conditionalRequired." ≡ conditionalRequired:. Each remaining sentence in those cases states a fact (why coalesce, the inverted validation, previous on insert, has() is not a null check); every assertion is unchanged. 212 bytes, as the body says (4181 − 212 + 1438 = 5407 stored at the head).
  • Ratchet row by ruling A, at the measurement. 1046 → 1352 ⇐ 5407 bytes stored (ceil(5407/4) = 1352), comment carries the pay-down first (53 units), the addition (359 units) and the tap verbatim. Headroom 0. Correct under the script's ceil(utf8 bytes / 4) convention.

② Semver level

@objectstack/skills takes a patch changeset (.changeset/26760-formula-label-eval.md), both declaration lines present and equal to the PR body. Eval fixtures publish no accept set — patch is right (the same reading as #26566 and #26774).

③ Boundary flags

  • Governed path (skills/**, Tier H): no approval yet; draft. This seat's account is not a GOVERNED_APPROVERS member; this record is not an approval and does not lift Tier H.
  • Check runs on 827f9351 at this record: 17+ success (incl. Governed Surface Queue Guard, Lint · scoped · tooling self-tests, Build Core, Test Core shards 3–4, Test UI), 11 expected skips, the rest still in_progress — among them Lint · repo & tooling gates (home of the token ratchet) and Check Changeset; the dev reports both green locally. Nothing red; a red among them re-opens this record.
  • Should-fix, outside this diff, same class as skills(objectstack-automation): eval fixtures 5, 7 and 8 of flows-triggers-approvals.json contradict their own assertions — expected_output spells a must_not_contain string or lacks a must_contain one, so the ideal answer fails its own check #26515: case 4 (untouched here, identical on main) spells both of its own must_not_contain strings inside its ideal answer — "never new Date() / Date.now()" carries new Date(, and "never today() + 45" carries today() + . A correct answer that repeats the teaching's own wording is graded wrong. Not this PR's hunk, so not a must-fix on it; it wants one card for the lane (or a same-byte rewording in a follow-up — the file is now at headroom 0, so any fix is paid in bytes). I could not confirm from here whether a card already names it.
  • No gate runs the eval; the green proves parse, ratchet and changeset, not that an agent passes the prompt (named, as on docs(skills): objectstack-ui gains an eval for a report's dateRange and exportOptions #26774).
  • Dev deviations: none beyond the ones judged above. The ACCEPT's item 2 ("each expected_output agrees with its assertions") holds for every case except case 4, which the seat did not touch.

④ Design judgment

  • cel-predicates-formulas.json cases 6–7 against SKILL.md:196 and the stdlib: the pair is the right shape for a macro that is correct in exactly one context — a positive case that rewards reaching for it where it belongs (a locale-following record name), and a negative case that refuses it where it faults (a predicate), with the stored-value comparison as the taught alternative. Each refusal maps to a behaviour the stdlib enforces or a key that does not exist; each must-contain is the skill's own spelling. The prompts give the field names and the expected locale readings, so a candidate cannot pass by paraphrase. Holds; nothing to break. The cuts in cases 1–5 remove only sentences the grader already enforces, so the file teaches the same and grades the same (case 3 now grades its own ideal answer right).

Implemented-by: claude/project-thread-6uuq7f
Reviewed-by: session_01ADHkmQr732toX135tyo5zs

VERDICT: PASS


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review October 27, 2026 22:50
@os-zhuang
os-zhuang enabled auto-merge October 27, 2026 22:50
@os-zhuang
os-zhuang added this pull request to the merge queue Oct 27, 2026
Merged via the queue into main with commit 40264be Oct 27, 2026
45 checks passed
@os-zhuang
os-zhuang deleted the claude/project-thread-6uuq7f branch October 27, 2026 23:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s tooling

Projects

None yet

3 participants