Skip to content

docs(skills): objectstack-automation teaches the CEL value envelope in fields / assignment values and / 100.0 for money - #22284

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-22260-automation-skill-value-slot-cel
Oct 8, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
claude/issue-22260-automation-skill-value-slot-cel

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #22260

Clause-②: no

skills/objectstack-automation/SKILL.md taught {token} values in create_record / update_record fields and the money sample {round(x * 100) / 100}. Since #19939 pass 1 landed (PR #22259 is merged; its changeset .changeset/19939-flow-value-slot-template-dialect-refused.md is the FROM → TO authority for every spelling below) the tree refuses those values at objectstack validate, registerFlow and the executors, and / 100 is integer division in CEL (#11182 ruling D item 2 prescribed / 100.0). #19939's second half (the two kept spellings) remains open. Tier H (skills/**): this PR stays draft and lands on an authorized APPROVED review or by the maintainer's hand — no seat readies, queues or arms it.

What changed — two files, skills/objectstack-automation/ only

SKILL.md (+33 / −32 lines on origin/main c8bb3c8d9c):

  • Item 1 of the authoring-traps list (:224-241) is rewritten around the value slot: in create_record / update_record fields.* and an assignment value (its two legacy shapes too) a string is the literal text it spells, a {…} token is refused at the three doors naming its CEL spelling, and a computed value is { dialect: 'cel', source: '…' } — one worked has() form ('{record.owner}' → source: 'has(record.owner) ? record.owner : null', with "nothing / null / a default is now YOUR call"), the array-index form ('{record.tags.0}' → 'record.tags[0]'), the money form (source: 'round(x * 100) / 100.0' — CEL divides two integers as integers, so / 100 turns 123.46 into 123), and the two spellings a value slot still accepts ({NOW()} / {TODAY() ± N}, {$User.PATH}). A second paragraph names the slots pass 1 does not touch — text slots (notify title / message, script inputs, http url / body, …) and filter keep the single-brace template — and keeps the text-slot grammar and its two traps ({{x}} is the template-field dialect; an unknown call-position name fails the node at run time).
  • Item 2 (:245-246): fields: { ref: '{newRec}' } / '{newRec.id}' → fields: { ref: { dialect: 'cel', source: 'newRec' } } (writes the whole record object) / source: 'newRec.id'.
  • Item 3's example (:273): fields: { ai_category: '{ai.ai_category}', ai_sentiment: '{ai.ai_sentiment}' } → fields: { ai_category: { dialect: 'cel', source: 'ai.ai_category' } } (one field; the second was the same spelling twice).
  • Filter-tokens section (:207-208, :213-214): fields dropped from "in title, message, fields and url a bare {current_year_start} is a nonsense reference" (a fields string is now literal text, not a reference at all) and "write payload" dropped from "(message body, http url, write payload) only renders an empty string — a warning" (a fields {record.x} is now an error at validate, not a warning).

evals/flows-triggers-approvals.json (eval 5): "persists {recalc.discount} via fields" → "persists it with fields: { discount: { dialect: 'cel', source: 'recalc.discount' } }"; must_contain gains dialect: 'cel', must_not_contain gains {recalc, so the eval now pins the new spelling and refuses the old.

Every {…} value spelling in the package, judged against the changeset's refused list

File :line on c8bb3c8d9c Spelling Slot Verdict Now
SKILL.md:224-226 "Value fields on a node's config (fields, inputs, notify message/title, …) interpolate {token}" puts fields in the template set false for fields item 1 rewritten; inputs and notify text keys stay in the template list
SKILL.md:232 {round(x * 100) / 100} value-slot teaching refused (expression), and integer division source: 'round(x * 100) / 100.0'
SKILL.md:235 / :237 body: '{{ai_reply}}' / body: '{ai_reply}' slot unnamed in a value slot {ai_reply} is refused (path); in a text slot still the template folded into the text-slot paragraph ({{x}} is the template-field dialect)
SKILL.md:236-237 ticket: '$source.id' / ticket: '{source.id}' fields-shaped key {source.id} refused (path) retired; the rule survives as "a string is the literal text it spells"
SKILL.md:238-241 '{ROUND(x, 2)}' / '{Math.round(x)}' / '{(x).toFixed(2)}' slot unnamed in a value slot the {…} is refused at validate first; in a text slot still fails the node at run time kept for text slots, one spelling
SKILL.md:245 fields: { ref: '{newRec}' } update_record field value refused (path) fields: { ref: { dialect: 'cel', source: 'newRec' } }
SKILL.md:246 fields: { ref: '{newRec.id}' } update_record field value refused (path) source: 'newRec.id'
SKILL.md:267 inputs: { ticketId: '{record.id}' } script.inputs not a value slot — screen-nodes.ts:343 still interpolates unchanged
SKILL.md:272 filter: { id: '{record.id}' } filter keeps the template unchanged
SKILL.md:273 fields: { ai_category: '{ai.ai_category}', ai_sentiment: '{ai.ai_sentiment}' } update_record field value refused (2 paths) one CEL envelope
SKILL.md:107-114 notify recipients / title / message / sourceId text slots keep the template unchanged
SKILL.md:154 "The handler reads {NODEID.error} (or run-wide {$error})" no slot named true in a text slot; in a value slot the refusal names vars["$error"].message unchanged (see Acceptance notes)
SKILL.md:208 / :213 "fields" / "write payload" in the filter-tokens prose — false since pass 1 dropped
references/examples-flows.md:41, :119-121 {TODAY()}, {TODAY() + N} filter keeps the template unchanged
references/examples-flows.md:44 fields: { status: 'escalated' } field value literal unchanged
references/state-machines-and-approvals.md:120 filter: { id: '{record.id}' }, fields: { stage: 'closed_won' } filter / literal field keep unchanged
evals/flows-triggers-approvals.json:7 renewal_date: { $lt: '{TODAY()}' }, fields: { status: 'lapsed' } filter / literal keep unchanged
evals/flows-triggers-approvals.json:17 notify recipients: '{record.owner_id}', "title with single-brace interpolation" text slots keep unchanged
evals/flows-triggers-approvals.json:47 "persists {recalc.discount} via fields" update_record field value refused (path) CEL envelope, pinned

references/_index.md is generator-owned and unchanged. No os:check-marked block changed (the two in this package are in references/*.md, untouched).

skills/** readings — the token ratchet, paid inside each file

File Before (c8bb3c8d9c) After (5ac59ff0da) Ceiling
SKILL.md 438 lines · 23125 bytes · 5782 tokens 439 lines · 23052 bytes · 5763 tokens 5785 (headroom 3 → 22)
evals/flows-triggers-approvals.json 55 lines · 5019 bytes · 1255 tokens 55 lines · 5018 bytes · 1255 tokens 1255 (headroom 0 → 2 bytes)
whole package, all 10 SKILL.md 4408 lines · 52572 tokens 4409 lines · 52553 tokens −19 tokens

Token = ceil(utf8 bytes / 4), the gate's own convention. No ceiling moved; nothing was re-wrapped to buy a line. Every deleted line and where its content survives:

  1. Item 1's grammar bullets ({var} / {record.title}, {record.tags.0} array index, {$User.Id} / {NOW()} / {TODAY() + 30}, the six functions "mirror the CEL stdlib 1:1", "anything without {…} is a literal") → compressed into the text-slot paragraph (same facts, now scoped to the slots that still read the template); "(e.g. a multiple: true lookup, stored as an array)" → "(array index)".
  2. "round is integer-only (no round(x, 2)); for N decimals write {round(x * 100) / 100} (scale 2)" → the money bullet (/ 100.0); round(x) → int is in objectstack-formula's stdlib table, which item 4 and the opening blockquote already point at.
  3. ❌ body: '{{ai_reply}}' → "{{x}} is the template-field dialect" in the text-slot paragraph.
  4. ❌ ticket: '$source.id' and ✅ body: '{ai_reply}', ticket: '{source.id}' → retired: in a value slot the ✅ spelling is now refused outright and the refusal names its CEL form; the literal rule survives as "a string is the literal text it spells" (value slots) and "no {…} ⇒ literal" (text slots).
  5. ❌ '{ROUND(x, 2)}' / '{Math.round(x)}' / '{(x).toFixed(2)}' "… with a named error naming the supported set. The build does not catch these (conditions are checked, call-position names are not)" → one spelling, the same rule ("fails the node at run time, unchecked at build, not fault-routable"), scoped to text slots — in a value slot the build refuses the {…} first.
  6. :277 the one-line defineStack({ functions: { 'helpdesk.aiTriageStub': … } }) registration — spelled a third time (item 3's head shows defineStack({ functions: { my_fn: … } }) and the second fence registers 'helpdesk.aiTriageStub') → deleted.
  7. :263 // ❌ DON'T: expect the function to update the record itself (it has no data API) → restated the prose two lines above the fence ("it does NOT read/write the database") → deleted; the ✅ line stays.

Eval file, paid by: "(FlowSchema has no top-level schedule; no cron tagged template)" → "(no top-level schedule; no cron tag)"; "nodes wired with edges (never next), ending in an end node" → "edges (never next), an end node"; "Failure routing is an edge { source, target, type: 'fault' }" → "a type: 'fault' edge" (the shape stays pinned by must_contain).

Measured at the tree, not recalled

Refusal control — built packages/spec/dist at 5ac59ff0da, flowNodeValueTemplateRefusals and UpdateRecordConfigSchema imported from @objectstack/spec/automation:

  • old :273 → 2 refusals, path=fields.ai_category / fields.ai_sentiment, label=update_record field value; each message leads with the VALUE_SLOT_TEMPLATE_REFUSAL sentence ("A value slot no longer reads the {…} template dialect: a string here is the literal text it spells …") then "Write {ai.ai_category} as { dialect: 'cel', source: 'ai.ai_category' }. CEL refuses an absent variable or key where the template wrote nothing, so guard one that may be absent with has(): has(ai.ai_category) ? ai.ai_category : null (the guarded form writes null)."
  • old :246 → 1 refusal at fields.ref: "Write {newRec.id} as { dialect: 'cel', source: 'newRec.id' } …"
  • old :232 → 1 refusal at fields.total: "Write {round(x * 100) / 100} as { dialect: 'cel', source: 'round(x * 100) / 100.0' }. Every division keeps a decimal operand: CEL divides two integers as integers, so round(x * 100) / 100 drops the decimals where round(x * 100) / 100.0 keeps them."
  • the new envelopes (ai.ai_category, newRec.id, round(x * 100) / 100.0, has(record.owner) ? record.owner : null) → 0 refusals; UpdateRecordConfigSchema.safeParse on the old sample → success: false at path ["fields","ai_category"], on the new sample → success: true.
  • the two survivors in fields ('{NOW()}', '{TODAY() + 30}', '{$User.Id}') → 0 refusals; notify title / message and script inputs carrying {record.id} → 0 (not value slots); legacy assignments: [{ variable, value: '{record.amount}' }] → 1 refusal at assignments[0].value.

CEL evaluation — built @objectstack/formula, ExpressionEngine.evaluate({ dialect: 'cel', source }, scope) with the engine's own celScope shape ({ extra: { ...vars, vars }, record: vars }, engine.ts:11791-11806), x = 123.456:

  • round(x * 100) / 100 ⇒ 123 · round(x * 100) / 100.0 ⇒ 123.46 · round(x * 100) ⇒ 12346
  • has(record.missing) ? record.missing : null ⇒ null · bare record.missing ⇒ error "No such key: missing" · record.tags[0] ⇒ "x" · has(vars.x) ? vars.x : null ⇒ 123.456
  • list[0] for a variable literally named list ⇒ error "Cannot index type 'type' with type 'int'" (list is a CEL type name) — see Acceptance notes.

Gates — local, at 5ac59ff0da

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on this tree derives 24 families from the two changed paths. All 24 ran, each exit code captured before any pipe; --ran reconciliation: "24 derived, 24 run, 0 NOT-MEASURED, 0 UNRUN (a DERIVED zero — all 24 recorded an exit code and none of them is 3)".

Command Exit
node scripts/check-skills-token-ratchet.mjs — "skills/objectstack-automation/SKILL.md is 5763 tokens (ceiling 5785; headroom 22)" · "54 authored bundle file(s) within their ceilings" 0
node scripts/check-skills-token-ratchet.mjs --self-test 0
pnpm check:skill-identifier-liveness — "Leg 1: 457 citation(s) over 53 published file(s) … Leg 2: 8 registered exhaustive section(s), 0 ledgered gap(s)" 0
pnpm check:corpus-claim-drift — "Scanned: 254 .md/.mdx file(s) … skills 53" 0
pnpm check:doc-authoring 0
pnpm check:nul-bytes — "scanned 10237 text file(s) … no raw ASCII control bytes" 0
pnpm check:skill-compatibility — "11 pinned major(s) all match the workspace (@objectstack/spec is 17.x)" 0
pnpm check:skill-frame-sync 0
pnpm check:role-word 0
pnpm --filter @objectstack/spec run check:skill-docs — "Skill docs in sync" 0
pnpm check:watch-hint-literal 0
pnpm --filter @objectstack/lint run check:doc-formula-expressions — first answered exit 3, "PREREQUISITE NOT MET — @objectstack/lint is not built" (not a measurement); after pnpm exec turbo run build --filter=@objectstack/lint --concurrency=2 under the verify lock (VERDICT command-exit 0) 0
node scripts/check-ci-filter-parity.mjs · check-closing-keyword-parity.mjs (+ --self-test) · check-comment-mask-corpus.mjs · check-doc-route-spelling.mjs --advisory (+ --self-test) 0 each
pnpm check:agent-test-spelling · check:cross-package-test-inputs · check:driver-memory-census · check:gitlink-declared · check:pm-governed-merges · check:refd-timer-probe 0 each

check:skill-examples not run as a control: no marked block changed. Not run locally (CI-owned): pnpm lint, the type-check lanes, Test Core — no package source changed. No package test / typecheck is owed: the diff touches no packages/**.

Changeset: none — skills/** publishes nothing from a released package; skip-changeset is applied with the PR assignee in one label-write call (recorded in the report comment on #22260).

Acceptance notes

  • Finding, for the seat to file (class c): the changeset's FROM → TO row '{list.0}' → { dialect: 'cel', source: 'list[0]' }, and celPath in packages/spec/src/automation/flow-value-slot-template.ts which every refusal message is built from, emit list[0] for a variable literally named list; under CEL list is a type name, so the remedy an author copies evaluates to "Cannot index type 'type' with type 'int'" (probe above). Affected: variables named after a CEL type (list, map, int, string, bool, double, uint, bytes, type, timestamp, duration, null_type). Reach: measured through the built formula engine, not through objectstack validate (whether validate accepts list[0] was not probed). Dedupe words: list[0] CEL type name, celPath value slot remedy, Cannot index type 'type', flow-value-slot-template list variable. The skill now teaches record.tags[0] instead.
  • Noted, not filed: SKILL.md:154 "The handler reads {NODEID.error} (or run-wide {$error})" names no slot; it holds in a text slot, and in a value slot the refusal names vars["$error"].message. Left as is (22 tokens of headroom); carrier: [v18] retire the {var} template dialect in flow assignment slots: refuse at registration with per-spelling remedies (the C half of #11182 ruling D, on the v18 train) #19939's second half, which will touch this item again.
  • Noted, not filed: the frontmatter description still routes "CEL expressions in flow conditions / edge guards" to objectstack-formula; value envelopes now also are CEL. Untouched because the frontmatter regenerates skills/README.md and content/docs/ai/skills-reference.mdx (check:skill-docs), outside this card's file surface; carrier: none.

维护者速读(草稿)

改了什么

发布版自动化技能(skills/objectstack-automation/SKILL.md)里教 AI 怎么给流程节点的 fields 赋值的那一条,改成了现在运行时真正接受的写法:create_record / update_record 的 fields 和 assignment 的值,要么是字面量,要么是 CEL 信封 { dialect: 'cel', source: '…' };旧的 {token} 单花括号写法在这些位置会被 objectstack validate、registerFlow 和执行器拒绝。同时把金额取两位小数的样例从 / 100 改成 / 100.0,补了「键可能不存在时用 has() 守一下」的写法,并明确写出仍旧接受的两种旧写法({NOW()} / {TODAY() ± N}、{$User.Id})和没有变化的位置(通知文案、inputs、http、filter 仍用单花括号模板)。配套的一条 eval 也改成期望新写法。两个文件各自在自己的 token 上限内付清,没有抬上限。

为什么改

这份技能随 npx skills add 发到每个客户项目,是 AI 写流程之前读的第一份材料。PR #22259 合并后,它教的 fields 写法会在校验时被拒;更糟的是金额样例 {round(x * 100) / 100} 没有任何门拦着——在 CEL 里是整数除法,123.46 会悄悄变成 123。实测:旧样例在构建后的 spec 上全部被拒并给出 CEL 写法;新样例全部通过;CEL 引擎里 / 100 得 123、/ 100.0 得 123.46。

风险与代价(含回滚)

只改了技能文本和一条 eval,不碰 packages/**,不发包、不需要 changeset。风险在措辞:每句话都对照了合并后的 changeset 和源码,24 个派生门禁本地全绿(含 token 棘轮、标识符存活、兼容版本)。回滚就是 revert 这一个 commit,没有数据或运行时影响。一个顺带发现(变量恰好叫 list 时,拒绝文案给出的 list[0] 在 CEL 里跑不通)不在本 PR 范围,留给席位立单。

席位意见

(留空)

你要做的

审阅后在本 PR 上给一个 APPROVED(Tier H,skills/**),或自行合并;不需要其他操作。


Generated by Claude Code

…n fields / assignment values and / 100.0 for money

A flow value slot (create_record / update_record fields.*, an assignment
value) no longer reads the single-brace template dialect: a string is the
literal text it spells and a {…} token is refused at objectstack validate,
registerFlow and the executor, naming the CEL spelling. The skill's item 1,
item 2 and the script → update_record example now teach the
{ dialect: 'cel', source: '…' } envelope, the has() guard for a key that
may be absent, and / 100.0 for money (CEL divides two integers as
integers), and keep the two spellings a value slot still accepts
({NOW()} / {TODAY() ± N}, {$User.PATH}) and the text slots and filter that
keep the template. The eval that pinned {recalc.discount} in fields now
expects the envelope. Token ratchet paid inside each file.

Claude-Session: https://claude.ai/code/session_01CXydFDyiQwNbGFkmwrcRQq
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation labels Oct 8, 2026
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 8, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 5ac59ff0da82eb668e631a2d7fffd2dc9b40918b
Local-runs: none

Reviewed in-seat by the skills seat 1 (dispatching seat; this session is served at the contract-review tier, read off get_session), 2026-10-08T12:22Z. Face hit: published skill text (skills/objectstack-automation/**, Tier H; dispatch-gates --tier MANDATORY). Read-only shape held: the diff (2 files, +33/−32), the card #22260 and its thread (the seat's unlock 6058882858, the claim 6058905926, the os-dev-report 6059461646), the head's check-runs; nothing built or run locally. Premises re-read on origin/main fbcbcf124f (it contains the branch base c8bb3c8d9c; skills/objectstack-automation/** is untouched between them; merge-tree clean).

① Derived judgments

  1. Slot boundary. The ledger on origin/main (packages/spec/src/automation/flow-node-expression-paths.ts, FLOW_NODE_EXPRESSION_PATHS) carries exactly three role: 'value' entries — assignment assignments.*, create_record fields.*, update_record fields.* — and flowNodeValueTemplateRefusals adds only the two legacy assignment shapes. The rewritten item 1 names those positions as the value slots and keeps notify title / message, script inputs, http url / body and filter on the single-brace template. Correct.
  2. Remedies are the judge's own strings (flow-value-slot-template.ts): celPath turns record.tags.0 into record.tags[0]; celExpression writes every integer divisor as a double (round(x * 100) / 100.0); guardOf emits has(record.owner) ? record.owner : null for a two-segment path. The skill's three worked forms are those strings. Correct.
  3. Kept spellings: KEPT_KINDS holds the date macro (NOW() / TODAY() with an optional ± N) and $User. paths, and a string carrying one of them is not refused. The skill's "still accepted" sentence names the same two and nothing else. Correct.
  4. Item 2 and the item-3 example now write envelopes (source: 'newRec' writes the record object, source: 'newRec.id' the id; ai.ai_category one field), and eval 5 pins dialect: 'cel' while refusing {recalc. The filter-tokens prose drops fields and "write payload", both false since PR feat(spec)!: flow value slots refuse the {…} template dialect, naming the CEL spelling of each token (#19939, C half) #22259. Correct.
  5. Ratchet, recomputed by the seat from the head blobs with the gate's convention (ceil of utf8 bytes over 4): SKILL.md 23052 bytes = 5763 tokens (ceiling 5785), the eval 5018 bytes = 1255 (ceiling 1255); no ceiling row changed. Payment is deletion of restated content (the ❌ / ✅ spellings whose value-slot form is now refused outright, the DON'T comment restating the prose two lines above it, the third defineStack({ functions }) registration), no re-wrap; each deleted fact survives where the PR body says. Correct.
  6. Scope: git diff --stat base..head = the two claimed files; no os:check block, no references/*.md, no packages/**. 24 gate families derived with dispatch-gates --commands, each exit captured, --ran reconciliation 24 derived / 24 run / 0 NOT-MEASURED; the one exit 3 (check:doc-formula-expressions, lint unbuilt) re-run to 0 after the build under the verify lock. The CI reading at this record's clock is in the ACCEPT on finding(skills): objectstack-automation SKILL.md teaches {token} values in create_record / update_record fields that PR #22259 (#19939 pass 1) refuses, and its money-rounding sample is CEL integer division #22260.

② Semver level

None. Published skill text and one eval; no package content, API or schema. skip-changeset is the correct declaration, applied by this seat after its own reading (the dev's label-write was refused by its session classifier and recorded in its report).

③ Boundary flags

Implemented-by: claude/issue-22260-automation-skill-value-slot-cel
Reviewed-by: session_01CXydFDyiQwNbGFkmwrcRQq

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读(终稿)

席位:skills 席 1,session_01CXydFDyiQwNbGFkmwrcRQq,2026-10-08T12:26Z。对照席位自己读的 diff(head 5ac59ff0da,两个文件 +33/−32)与 origin/main 上判官的源码(packages/spec/src/automation/flow-value-slot-template.ts、flow-node-expression-paths.ts)校正 dev 草稿;契约复核记录(PASS)在本 PR 上一条评论,ACCEPT 在卡片 #22260。

改了什么:发布版自动化技能(skills/objectstack-automation/SKILL.md)里教 AI 给流程节点 fields 赋值的那一条,改成运行时现在真正接受的写法:create_record / update_record 的 fields 和 assignment 的值,要么是字面量,要么是 CEL 信封 { dialect: 'cel', source: '…' };旧的 {token} 单花括号写法在这些位置会被 objectstack validate、registerFlow 和执行器拒绝(PR #22259 落地的 #19939 前半)。金额取两位小数的样例从 / 100 改成 / 100.0;补了「键可能缺席时用 has() 守一下」和数组下标 [0] 的写法;写明仍接受的两种旧写法({NOW()} / {TODAY() ± N}、{$User.Id})和没变的位置(通知文案、inputs、http、filter 仍用单花括号模板)。第 2、3 条的示例改成信封写法;配套 eval 第 5 条改成期望新写法并拒绝旧写法。两个文件都在各自 token 上限内付清(5763/5785、1255/1255),没抬上限。

为什么改:这份技能随 npx skills add 发到每个客户项目,是 AI 写流程前读的第一份材料;#22259 合并后它教的 fields 写法会在校验时被拒,而金额样例 {round(x * 100) / 100} 没有任何门拦着——在 CEL 里是整数除法,123.46 悄悄变成 123。dev 在构建后的 spec 上实测:旧样例全部被拒并给出 CEL 写法,新样例全部通过;CEL 引擎里 / 100 得 123、/ 100.0 得 123.46。

风险与代价(含回滚):只改技能文本和一条 eval,不碰 packages/**,不发包、无 changeset(skip-changeset)。风险在措辞:每句话对照了合并后的 changeset 与源码,24 个派生门禁本地全绿。回滚 = revert 落地的那一个 commit。

席位意见:同意合入,席内契约复核 PASS。席位自己在 origin/main 上核过三件事:值槽只有账本里三个 role: 'value' 位置(assignment / create_record / update_record)加 assignment 的两种旧形,文本划的边界与此一致;has()、[0]、/ 100.0 三种写法与判官的 guardOf / celPath / celExpression 逐字一致;仍接受的两种旧写法就是 KEPT_KINDS。顺带发现(变量恰好叫 list 时,拒绝文案给出的 list[0] 在 CEL 里是类型名、跑不通)由本席另立 spec 车道 finding 卡,不在本 PR。两处「看到但未改」(:154 的 {$error} 未指明槽位;frontmatter 仍只把条件里的 CEL 路由到 formula 技能)由 #19939 后半承接。

你要做的:用授权账号(os-zhuang 或 hotlong)在本 PR 上 APPROVE 一次,或亲手合入。PR 保持 draft;收到授权 APPROVED 后由本席落地并收口 #22260(若批准者已自行翻 ready 入队,本席只做收口)。


Generated by Claude Code

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 skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants