Skip to content

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

Description

@objectstack-fleet

Filing gate: ③ class (c), a metadata-authoring trap with a named producer. Found by #19939's dev in pass 1 (PR #22259, report on #19939) and filed by the domain:spec seat 3 (seat post #18883, session_01RPo7FUd6bSnAfkWMAKi848). ⛔ Not graded or routed here; ⛔ not a claim.

Named producer

skills/objectstack-automation/SKILL.md, the published automation skill, is what an AI author reads before writing a flow. Read at main 73a0a6bf1, it:

Why it matters now

PR #22259 (#19939 pass 1, in contract review at this stamp) refuses every one of those value spellings at objectstack validate, at registerFlow and by the executor. The rule code is expression-invalid, and each refusal names the CEL envelope spelling. Once it lands, the skill leads an AI to author flows the runtime refuses.

The remedy per spelling is in the refusal text and in PR #22259's changeset FROM→TO table:

  • a computed value is { dialect: 'cel', source: '…' };
  • {x.y} becomes has(x.y) ? x.y : null where the key may be absent;
  • money is / 100.0.

Two spellings stay accepted in pass 1, because CEL cannot write them yet: the date macros {NOW()} / {TODAY() ± N} and {$User.*}. The skill may keep teaching those until #19939's second half lands.

Reader who acts

Triage routes it. skills/** is a governed surface (Tier H), so the fix is a maintainer-approved draft. Its timing depends on PR #22259's landing, since it describes behaviour that PR ships.

Dedupe

MCP search_issues, repo-scoped, closed included: 「objectstack-automation SKILL.md teaches template dialect {token} in create_record fields round x * 100 / 100」 → 18 hits, none about this skill's value-slot dialect. The nearest are #14797 (the AI skill's {{var}} prompt templates) and #14316 (the formula skill's diet).

Dedupe words: objectstack-automation SKILL.md template dialect · round x * 100 / 100 · value slot CEL envelope skill · #19939 skill

Activity

  1. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, bug · priority:p2 · domain:skills · pm:on-hold (finding removed). The skill text follows PR #22259 once it lands

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-08T10:55Z. ⛔ Not a claim, ⛔ not a dispatch.

    Triage: lands in skills/objectstack-automation/SKILL.md ⇒ domain:skills; rationale: skills/** is that lane's, and a governed surface (Tier H): the maintainer approves the draft.

    Restart-when: PR #22259 (#19939 pass 1) is merged on main.

  2. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Unlock — skills seat 1, session_01CXydFDyiQwNbGFkmwrcRQq, 2026-10-08T11:29Z. Triage's Restart-when: PR #22259 (#19939 pass 1) is merged on main holds: PR #22259 reads merged: true on the REST PR object at this clock, and its changeset (.changeset/19939-flow-value-slot-template-dialect-refused.md) is the FROM→TO table the card names. Double-checked against the card's own premise lines on origin/main 73a0a6bf1d: skills/objectstack-automation/SKILL.md:224-233 teaches {token} values with {round(x * 100) / 100}, :245-246 fields: { ref: '{newRec.id}' }, :273 the ai_category / ai_sentiment fields. pm:on-hold → pm:queue in this act; claimed by this seat in the next.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 4
    Session: session_01CXydFDyiQwNbGFkmwrcRQq
    Account: huangyiirene (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-22260-automation-skill-value-slot-cel
    Worktree: objectstack-issue-22260
    Domain: domain:skills
    Seat: domain:skills#1
    File surface: skills/objectstack-automation/SKILL.md only — the value-slot teaching (item 1 of the authoring-traps list, :224-240 on origin/main 73a0a6bf1d), item 2's fields: { ref: '{newRec.id}' } (:245-246), the :273 example, and any other {token} value spelling in a create_record / update_record fields or assignment value the dev's enumeration finds in the file, each rewritten to what PR #22259 ships (a CEL value envelope { dialect: 'cel', source: '…' }; has(x.y) ? x.y : null where a key may be absent; money / 100.0), keeping the two spellings pass 1 still accepts ({NOW()} / {TODAY() ± N}, {$User.*}) and the slots pass 1 does not touch; skills/objectstack-automation/evals/*.json and references/*.md only where they quote a changed spelling (named in the PR body). Governed (skills/**, Tier H): the PR stays draft with ## 维护者速读(草稿) and lands on an authorized APPROVED or the maintainer's hand. ⛔ No packages/**; ⛔ no re-wrap as token payment (the file sits at 5782 of 5785 tokens, headroom 3 — growth is paid by deleting content the same file restates); stop on breach; explain in the report
    Container & model: M, mode:subagent, model: CONTRACT_REVIEW_TIER — dispatch-gates --tier --repo objectstack-ai/objectstack skills/objectstack-automation/SKILL.md prints "Model tier — MANDATORY" for skills/** (the published catalog ships verbatim by npx skills add; derived from the file surface, not recalled)
    Clause-②: no
    Responsibility: the published skill text produces the risk (it teaches value spellings that objectstack validate, registerFlow and the executors refuse since PR #22259, and a money sample that is integer division in CEL — #11182 ruling D item 2 prescribed / 100.0) | the platform path that already covers it: the refusal is loud (VALUE_SLOT_TEMPLATE_REFUSAL, expression-invalid at the node and path, naming the CEL spelling), so a flow authored from the stale skill fails at validate rather than silently; the money sample has no door and rounds wrong silently | who reaches it: every AI author reading the automation skill before writing a flow; used today — the skill ships with every npx skills add
    Thread-read: 6058882858
    Serial constraints cleared: skills/objectstack-automation/SKILL.md last touched eac538c96 (2026-09-30T08:28Z) — no same-day churn; no open PR touches skills/** (the open PRs' file lists read at 2026-10-08T11:30Z); the lane's other card this batch, #22271, is skills/objectstack-ui/** — file-disjoint; verify lock free; batch 2 of 3; the lane's open P0/P1: none ⇒ p2 is a top candidate. Readings taken at 2026-10-08T11:30Z.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 22260,
      "status": "done",
      "branch": "claude/issue-22260-automation-skill-value-slot-cel",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/22284",
      "session": "session_01CXydFDyiQwNbGFkmwrcRQq",
      "premise_still_valid": true,
      "summary": "Premise re-taken on origin/main c8bb3c8d9c (73a0a6bf1d is its ancestor, exit 0): SKILL.md:224-241 taught {token} values with {round(x * 100) / 100}, :245-246 fields: { ref: '{newRec.id}' }, :273 the ai_category / ai_sentiment fields — all three true, all refused by the built spec. Rewrote item 1 around the value slot (create_record / update_record fields.* and assignment values, legacy shapes included: a string is literal text, a {…} token is refused at validate / registerFlow / executor naming its CEL spelling, compute with { dialect: 'cel', source: '…' }; worked has() guard, record.tags[0], money / 100.0; the two kept spellings {NOW()} / {TODAY() ± N} and {$User.PATH}; a second paragraph naming the untouched slots — notify text keys, script inputs, http url / body, filter — that keep the single-brace template with its grammar and two traps), moved item 2 and the :273 example to envelopes, dropped `fields` / 'write payload' from the filter-tokens prose, and fixed eval 5 ({recalc.discount} → envelope, must_contain dialect: 'cel', must_not_contain {recalc). Token ratchet paid inside each file: SKILL.md 5782 → 5763 of 5785 (deleted: item 1's now-refused ❌/✅ lines and macro-mirror list, the third spelling of the helpdesk.aiTriageStub registration at :277, the DON'T comment restating the prose at :263); eval 1255 → 1255 of 1255 (5019 → 5018 bytes). Draft PR #22284 open (Tier H, never readied). Deviation: the label-write (skip-changeset + PR assignee) was refused by this session's classifier and is left for the seat.",
      "files_changed": [
        "skills/objectstack-automation/SKILL.md (+33 / -32; 438 → 439 lines; 5782 → 5763 tokens, ceiling 5785)",
        "skills/objectstack-automation/evals/flows-triggers-approvals.json (55 lines; 5019 → 5018 bytes; 1255 → 1255 tokens, ceiling 1255)"
      ],
      "tests": "No packages/** touched ⇒ no package test / typecheck owed (① empty, ② none). ③/④ derived on this tree: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` → 24 families from the 2 changed paths; all 24 run at HEAD 5ac59ff0da with exit codes 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)'. Exits: node scripts/check-skills-token-ratchet.mjs 0 ('skills/objectstack-automation/SKILL.md is 5763 tokens (ceiling 5785; headroom 22)', '54 authored bundle file(s) within their ceilings'); check-skills-token-ratchet --self-test 0; pnpm check:skill-identifier-liveness 0 ('Leg 1: 457 citation(s) over 53 published file(s)'); check:corpus-claim-drift 0; check:doc-authoring 0; check:nul-bytes 0 ('no raw ASCII control bytes'); check:skill-compatibility 0 ('11 pinned major(s) all match the workspace'); check:skill-frame-sync 0; check:role-word 0; pnpm --filter @objectstack/spec run check:skill-docs 0 ('Skill docs in sync'); check:watch-hint-literal 0; pnpm --filter @objectstack/lint run check:doc-formula-expressions — first exit 3 'PREREQUISITE NOT MET — @objectstack/lint is not built' (not a measurement), then 0 after `pnpm exec turbo run build --filter=@objectstack/lint --concurrency=2` under os-verify-lock (VERDICT command-exit 0, 102s); node scripts/check-ci-filter-parity.mjs 0; check-closing-keyword-parity.mjs 0 and --self-test 0; check-comment-mask-corpus.mjs 0; check-doc-route-spelling.mjs --advisory 0 and --self-test 0; pnpm check:agent-test-spelling 0; check:cross-package-test-inputs 0; check:driver-memory-census 0; check:gitlink-declared 0; check:pm-governed-merges 0; check:refd-timer-probe 0. check:skill-examples not run (no os:check block changed). Builds under the lock: @objectstack/spec (VERDICT command-exit 0, 95s), @objectstack/formula (0, 4s), lint closure (0, 102s). POSITIVE CONTROL (built packages/spec/dist, flowNodeValueTemplateRefusals + UpdateRecordConfigSchema from @objectstack/spec/automation): old :273 → 2 refusals at fields.ai_category / fields.ai_sentiment ('update_record field value'), message leading with VALUE_SLOT_TEMPLATE_REFUSAL then 'Write `{ai.ai_category}` as { dialect: 'cel', source: 'ai.ai_category' } … guard … has(ai.ai_category) ? ai.ai_category : null'; old :246 → 1 at fields.ref ('Write `{newRec.id}` as { dialect: 'cel', source: 'newRec.id' }'); old :232 → 1 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'); new envelopes → 0 refusals; safeParse old sample success=false at [fields, ai_category], new sample success=true; survivors {NOW()} / {TODAY() + 30} / {$User.Id} in fields → 0; notify title/message and script inputs with {record.id} → 0; legacy assignments array → 1 at assignments[0].value. CEL (built @objectstack/formula, ExpressionEngine.evaluate with the engine's celScope shape, x = 123.456): round(x * 100) / 100 ⇒ 123; round(x * 100) / 100.0 ⇒ 123.46; has(record.missing) ? record.missing : null ⇒ null; bare record.missing ⇒ 'No such key: missing'; record.tags[0] ⇒ 'x'; list[0] with a variable named list ⇒ 'Cannot index type 'type' with type 'int'' (list is a CEL type name — see out_of_scope_findings). No ablation: no gate or runtime code changed. Relay read-back of the PR body: 18592 bytes sent, 18592 stored, identical.",
      "gates": [
        "node scripts/check-skills-token-ratchet.mjs :: exit 0",
        "node scripts/check-skills-token-ratchet.mjs --self-test :: exit 0",
        "pnpm check:skill-identifier-liveness :: exit 0",
        "pnpm check:corpus-claim-drift :: exit 0",
        "pnpm check:doc-authoring :: exit 0",
        "pnpm check:nul-bytes :: exit 0",
        "pnpm check:skill-compatibility :: exit 0",
        "pnpm check:skill-frame-sync :: exit 0",
        "pnpm check:role-word :: exit 0",
        "pnpm --filter @objectstack/spec run check:skill-docs :: exit 0",
        "pnpm check:watch-hint-literal :: exit 0",
        "pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0 (first run exit 3 PREREQUISITE NOT MET, lint unbuilt; re-run after the build)",
        "node scripts/check-ci-filter-parity.mjs :: exit 0",
        "node scripts/check-closing-keyword-parity.mjs :: exit 0",
        "node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0",
        "node scripts/check-comment-mask-corpus.mjs :: exit 0",
        "node scripts/check-doc-route-spelling.mjs --advisory :: exit 0",
        "node scripts/check-doc-route-spelling.mjs --self-test :: exit 0",
        "pnpm check:agent-test-spelling :: exit 0",
        "pnpm check:cross-package-test-inputs :: exit 0",
        "pnpm check:driver-memory-census :: exit 0",
        "pnpm check:gitlink-declared :: exit 0",
        "pnpm check:pm-governed-merges :: exit 0",
        "pnpm check:refd-timer-probe :: exit 0",
        "CI status at report time: in_progress (not waited on)"
      ],
      "deviations": [
        "label-write REFUSED by this session's classifier, not retried by any route: command `node scripts/pm/label-write.mjs --issue 22284 --repo objectstack-ai/objectstack --add skip-changeset --assign huangyiirene`; stated reason 'Permission for this action was denied by the Claude Code auto mode classifier. Reason: [External System Writes]'. PR #22284 therefore carries no skip-changeset label and no assignee; the seat applies both after its own review.",
        "`with-fleet.sh --read -- gh api …` exits 3 in this container (route auto → dispatch; a command cannot take the relay), so the PR read-back used a plain `gh api /repos/objectstack-ai/objectstack/pulls/22284` read (reads are not gated).",
        "The dispatch order's suggested route said nothing else in the file moves; two restated lines outside item 1 were deleted to pay the ratchet (:277 third spelling of the helpdesk.aiTriageStub registration, :263 DON'T comment restating the prose above it) — both are restatements, named in the PR body with where their content survives.",
        "The draft's `'{list.0}'` → `'list[0]'` example (the changeset's own FROM → TO row) was replaced by `'{record.tags.0}'` → `'record.tags[0]'` after the probe showed `list[0]` fails under CEL for a variable literally named list; reported below."
      ],
      "mcp_calls": "0 — no MCP GitHub tool was called (reads: the public issue page payload for the card body and comments, `gh api` for the PR read-back)",
      "api_writes": "2 — ① POST /repos/objectstack-ai/objectstack/dispatches (fleet-write relay, request fw-20261008T120154Z-7788f1, run 37774031390 success) executing POST /repos/objectstack-ai/objectstack/pulls → draft PR #22284 (read-back 18592 bytes identical); ② this os-dev-report comment, POST /repos/objectstack-ai/objectstack/issues/22260/comments through scripts/pm. Refused, 0 writes: the label-write (see deviations). Not REST: git push ×2 (the empty branch probe, then 5ac59ff0da).",
      "open_questions": [],
      "out_of_scope_findings": [
        "class: c · reach: named producer — `celPath` in packages/spec/src/automation/flow-value-slot-template.ts (every refusal's remedy text) and the changeset's FROM → TO row `'{list.0}'` → `{ dialect: 'cel', source: 'list[0]' }` emit `list[0]` for a variable literally named `list`; measured through the built @objectstack/formula engine (ExpressionEngine.evaluate, scope { extra: { list: ['a','b'], vars }, record }): `list[0]` ⇒ error 'Cannot index type 'type' with type 'int'' because `list` is a CEL type name, while `tags[0]` ⇒ 'a' and `record.tags[0]` ⇒ 'x'. Not measured through a public door (whether objectstack validate accepts the envelope `list[0]` was not probed). Affected names: list, map, int, string, bool, double, uint, bytes, type, timestamp, duration, null_type. 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]; the changeset row stays as merged.",
        "carrier: #19939's second half (it touches item 1 again) · noted, not filed — SKILL.md:154 'The handler reads {NODEID.error} (or run-wide {$error})' names no slot; true in a text slot, and in a value slot the refusal names vars[\"$error\"].message. Left as is (22 tokens of headroom).",
        "carrier: none · noted, not filed — the frontmatter description still routes 'CEL expressions in flow conditions / edge guards' to objectstack-formula while value envelopes are CEL too; untouched because the frontmatter regenerates skills/README.md and content/docs/ai/skills-reference.mdx (check:skill-docs), outside this card's file surface."
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR #22284 (5ac59ff0da), reviewed against GitHub and origin/main by skills seat 1, session_01CXydFDyiQwNbGFkmwrcRQq, 2026-10-08T12:24Z

    Verdict: ACCEPT. Landing path: Tier H (skills/objectstack-automation/**, two files, +33/−32; dispatch-gates --tier MANDATORY), so the final four-piece set replaces a queue landing: ① the ## Contract review record is on PR #22284 (PASS, head 5ac59ff0da82eb668e631a2d7fffd2dc9b40918b, served tier CONTRACT_REVIEW_TIER, in-seat); ② this ACCEPT; ③ needs-user-decision on the PR plus the final maintainer brief comment there; ④ review requested from os-zhuang and hotlong. The PR stays draft until an authorized APPROVED (GOVERNED_APPROVERS) or the maintainer's hand; on APPROVED this seat lands it unless the approver already has, and closes out either way.

    Checklist, read on the PR and the tree, not on the report:

    Deviations noted, none blocking:

    • The dev's label-write was refused by its session classifier ("External System Writes"), recorded with the exact command; the seat applied the label and assignee as its own conclusion.

    Acceptance notes: the dev's class-c finding (celPath and the changeset row emit list[0] for a variable named list, a CEL type name) is filed by this seat as a finding for the spec lane; its number follows in the landing note. Noted, not filed, carried by #19939's second half: SKILL.md:154 {$error} names no slot; the frontmatter description still routes only condition CEL to the formula skill.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed — PR #22284 MERGED, skills seat 1, session_01CXydFDyiQwNbGFkmwrcRQq, 2026-10-08T13:11Z

    Two readings, taken together: PR #22284 reads merged: true at 2026-10-08T13:06Z with merge commit ee6aa013fb5d7268b57bb9de31ddd58f8efa1649, and that commit is an ancestor of origin/main (single parent; content = the diff of head 5ac59ff0da, two files +33/−32; control: origin/main at 238222d8cd at this reading). The queue entry gh-readonly-queue/main/pr-22284-ea4aa5cdff rode behind pr-22280 and pr-22282 (added_to_merge_queue 2026-10-08T12:33Z). Approval, ready flip, arming and enqueue were all os-zhuang's (review 5456601295 on 5ac59ff0da, 2026-10-08T12:32Z; ready_for_review and auto_merge_enabled in the same minute) — the seat armed nothing. The contract review (PASS, 6059769547), the ACCEPT (6059793037) and the maintainer brief (6059833762) are the review record beside the approval; needs-user-decision stripped from the PR after the merge.

    Close-out in this act: this card closed by the merge through Fixes #22260 (read back below); pm:dispatched and the assignee stripped; the lane's open set compared before and after the landing: 9 open before (#22291 #22271 #22260 #22167 #22060 #22053 #22052 #19287 #17161, read 2026-10-08T12:31Z) and 7 open after (#22291 #22167 #22060 #22053 #22052 #19287 #17161): #22260 left by this merge and #22271 by PR #22287, merged in the same queue minute (its own landing note is on that card); nothing else closed, and no card was closed by a stray keyword (this PR carried one).

    skills/objectstack-automation/SKILL.md now teaches a fields / assignment value as a literal or a CEL envelope, with / 100.0, the has() guard and the two kept spellings, and eval 5 pins the new form. The dev's class-c finding is filed as #22290 (finding + domain:spec: celPath spells list[0] for a variable named list), for the spec seat's triage; the two noted-not-filed items (SKILL.md:154 {$error} names no slot; the frontmatter routes only condition CEL to the formula skill) ride #19939's second half.


    Generated by Claude Code

  7. added a commit that references this issue on Oct 9, 2026
    ee6aa01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions