Repository navigation
skills(objectstack-automation): SKILL.md and eval id 2 still teach single-brace {token} in notify text slots, which PR #22315 now refuses (Tier H, owed at #22110) #22454
Description
Activity
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsTriage: first grade,
priority:p2·domain:skills·target:v18·area:workflow·pm:queue(findingremoved)Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-09T10:01Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: the edit is to
skills/objectstack-automation/**(SKILL.mdandevals/flows-triggers-approvals.json). That puts it indomain:skills. It is governed text (Tier H), so the PR lands through the governed-surface endgame: a maintainer's APPROVED review, then the owning seat.- Why p2 and
target:v18: [v18] flow text slots: read ADR-0032 §3's{{ }}delimiter instead of single-brace{token}(notify title/message and the other flow string slots), converting only what renders the same #22110 (target:v18, p2) closed at 2026-10-09T09:41Z through PR feat(spec)!: flow text slots read the {{ }} delimiter, refusing a single-brace token with its hole spelling (#22110) #22315. Since then the skill teaches a notifytitle/messagethatmainrefuses at the build doors and the save door. An agent that follows the skill writes a flow that fails validation. That is the AI-error axis, on the v18 line. Same grade as its parent. - Dedupe: search finds only this card. finding(skills): objectstack-automation SKILL.md teaches
{token}values in create_record / update_recordfieldsthat PR #22259 (#19939 pass 1) refuses, and its money-rounding sample is CEL integer division #22260 (closed) is the value-slot sibling for PR feat(spec)!: flow value slots refuse the{…}template dialect, naming the CEL spelling of each token (#19939, C half) #22259. It is not this.
Direction: the card's own scope. I checked the three sites on
main2b61f2d9d:SKILL.md:108–:109: the notify example;SKILL.md:236–:241: the text-slot paragraph;- eval id 2: its
expected_output.
Restate them in
{{ }}, as ADR-0032 §3 and PR #22315's changeset spell it.- In the same paragraph, re-read each remaining slot it names (
inputs,httpurl/body,filter) against whatmainnow enforces, including [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 passes. Each sentence then says what the door does today. ⛔ No guidance beyond whatmainenforces. - Pins: the skill's own gates and its eval run green. A
git grepfor single-brace notify examples underskills/objectstack-automation/**returns 0.
- Why p2 and
- addedarea:workflowApprovals and automation — the work that runs without a person driving itApprovals and automation — the work that runs without a person driving itpriority:p2Medium: important, M3Medium: important, M3and removed
on Oct 9, 2026 objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsClaim: PM loop round 3
Session:session_01JmWtcHfGbC4ncw4GFKWuRA
Account:os-elon-musk(the seat's linked user asGET /useranswers it; always the card's assignee)
Branch:claude/issue-22454-automation-skill-text-slot-delimiter
Worktree:objectstack-issue-22454
Domain:domain:skills
Seat:domain:skills#1
File surface:skills/objectstack-automation/SKILL.md(onorigin/main587bd969: the notify example at lines 103–116 —title: 'Done: {record.title}'/message: 'Closed by {$User.Id}'— and the text-slot paragraph at lines 236–241 saying text slots keep the single-brace template; each remaining slot that paragraph names (inputs,httpurl/body,filter) is re-read against whatmainenforces today —packages/spec/src/automation/flow-text-slot-template.tsfor the text slots, the value-slot rules for the rest — and restated to what the doors do, ⛔ no guidance beyond whatmainenforces),skills/objectstack-automation/evals/flows-triggers-approvals.jsoneval id 2 (itsexpected_outputasks for "titlewith single-brace interpolation" and itsassertions.must_not_containcarries"{{"— both flipped to the{{ }}delimiter so the eval asserts whatmainaccepts), plus any other single-brace TEXT-slot example the enumeration pin hits underskills/objectstack-automation/**(the seat's own reading: agit grepfortitle/message/descriptionkeys carrying a{…}token hits exactly SKILL.md:108–109;{{appears once in SKILL.md and once in the eval). Tier H (skills/**is governed): the PR stays draft with the four-piece set and the maintainer merges it. ⛔ No file held by an open PR (15 open PRs' file lists read at 2026-10-09T11:17Z: none touchesskills/**). Net-line budget forskills/objectstack-automation/**(1,092 lines today, SKILL.md 439): 0 preferred, at most +2 with a stated reason per line; ⛔ no re-wrap; operative text carries no issue number. Stop on breach; explain in the report.
Container & model:S,mode:subagent,model: CONTRACT_REVIEW_TIER(MANDATORY:dispatch-gates --tier --repo objectstack-ai/objectstack skills/objectstack-automation/SKILL.md skills/objectstack-automation/evals/flows-triggers-approvals.json⇢ 'skills/**' — clause ① (2026-09-10 ruling, verbatim 「必须 fable的还包括对外发布的skills」): the published catalog ships verbatim to third parties bynpx skills add objectstack-ai/objectstack/skillsandnpm create objectstack, so a wrong edit is installed elsewhere before anyone here reads it — and no one-line exemption applies under this root)
Clause-②: no
Responsibility: the published skill text produces the risk (SKILL.md:108–109 and :236–241 prescribe the single-brace delimiter in notify text slots, which the build doors and the save door refuse since PR #22315 with the hole spelling as the remedy) | the platform path that already covers it: the doors' refusal — loud, after the author has written the wrong flow | who reaches it: every AI author who installsobjectstack-automationbynpx skills add, on the v18 line; used today
Thread-read: 6078701718
Serial constraints cleared:skills/objectstack-automation/last touchedee6aa013(2026-10-08) — no same-day churn; no open PR touchesskills/**; this seat's own PR #22458 (.claude/**,scripts/pm/label-write.mjs, in the merge queue) and PR #22460 (scripts/pm/fleet-write/*,rest-channel.md, re-merging behind the landed #22457) are file-disjoint from this card; the value-slot sibling #19939 ispm:queuein the spec lane and untouched here (this card rewrites text-slot guidance and re-reads the value-slot sentence againstmainwithout widening it); verify lock LOCKED; the lane holds no open p1. Readings at 2026-10-09T11:17Z.objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 22454, "status": "done", "branch": "claude/issue-22454-automation-skill-text-slot-delimiter", "pr": "https://github.com/objectstack-ai/objectstack/pull/22475", "head": "057ce436", "session": "session_01JmWtcHfGbC4ncw4GFKWuRA", "premise_still_valid": true, "summary": "The three sites now say what `main` enforces: the SKILL.md notify example reads `title: 'Done: {{ record.title }}'` and `message: 'Closed {{ record.closed_at | date:long }}'` (the old `{$User.Id}` body has no hole spelling — the door says to compute it into a variable first — so the example became a path-plus-formatter the door accepts and renders); the dialect paragraph is three statements, one door each (text slots render `{{ }}` holes and `os validate` / `registerFlow` refuse a `{…}` token with its `{{ }}` spelling; `{$User.Id}` / `{NOW()}` have no hole and `{{ $User.Id }}` renders blank — assign a variable first; `recipients` / `inputs` / `http` / `filter` keep the single brace), the `{{x}}`-is-template-field clause deleted; eval id 2 expects `title: 'Won: {{ record.name }}'`, `must_contain` gains `{{`, `must_not_contain` drops it. Two § Filter tokens sentences that listed text slots as single-brace positions were corrected, and the section's lede (restated twice elsewhere) was deleted to pay the token ratchet. Draft PR #22475 against main, Tier H, never readied; skip-changeset + assignee os-elon-musk written through label-write.", "tests": "Doors measured on the built dists (lint closure built under the verify lock at 057ce436, 129 s): NotifyConfigSchema.safeParse — new example ACCEPTED; old `title: 'Done: {record.title}'` REFUSED 'Write `Done: {record.title}` as `Done: {{ record.title }}`'; old `message: 'Closed by {$User.Id}'` REFUSED '`{$User.Id}` is not a variable, so no hole spells it: compute it into a variable with an `assignment` node … and write `{{ v }}` here'; eval title `'Won: {{ record.name }}'` ACCEPTED; `'By {{ $User.Id }}'` ACCEPTED. validateExpression('template', …) compiles all four. templateEngine.evaluate: 'Done: {{ record.title }}' → 'Done: Fix login'; 'Closed {{ record.closed_at | date:long }}' → 'Closed October 9, 2026'; 'By {{ $User.Id }}' → 'By ' (ok: true, blank). Pin: git grep -P for a title/message/description key whose quoted value holds a `{` with no `{` on either side — base d87dff67: SKILL.md:108, :109; head 057ce436: 0 hits in skills/objectstack-automation and 0 across skills/. Token ratchet: SKILL.md 5783/5785, eval 1253/1255, exit 0. Control-byte grep over both files: none. Skill eval run: NOT MEASURED — no runner in this repository reads must_contain / must_not_contain (tree grep finds only the eval JSONs and two READMEs); objectui's check-skill-eval-tokens.mjs matches whole tokens case-sensitively, degrading to an exact substring at a non-identifier end.", "gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (change set from merge base d87dff67, no paths) derived 24 commands at 057ce436 — identical to the path-derived list the dispatch named; each run with its exit captured before any pipe (ran.list `cmd :: exit N`); all 24 exit 0 with verdict lines read (check-skills-token-ratchet + self-test, check:skill-identifier-liveness, check:skill-docs, check:doc-formula-expressions, check:skill-compatibility, check:skill-frame-sync, check:nul-bytes, check:doc-authoring, check:corpus-claim-drift, check:cross-package-test-inputs, check:pm-governed-merges, check:agent-test-spelling, check:driver-memory-census, check:gitlink-declared, check:refd-timer-probe, check:role-word, check:watch-hint-literal, check-ci-filter-parity, check-closing-keyword-parity + self-test, check-comment-mask-corpus, check-doc-route-spelling --advisory + self-test). --ran reconciliation: 24 derived, 24 run, 0 NOT-MEASURED, 0 UNRUN (exit 0). Derivation printed STALE TREE — 3 commits behind origin/main (c512c255), two derivation inputs changed (scripts/check-durability-degradation-log-level.mjs, scripts/measure-durability-swallow-family.mjs), neither in the list; origin/main not merged in (its commits carry merge=os-regen spec artifacts). CI on the PR: in_progress at report time; not waited for.", "line_budget": "SKILL.md 439 → 438 lines (−1), 23,052 → 23,129 bytes (+77), ratchet 5,763 → 5,783 tokens (ceiling 5,785, headroom 2); eval 55 → 55 lines, 5,018 → 5,012 bytes (−6), 1,255 → 1,253 tokens (ceiling 1,255); package 1,092 → 1,091 lines. No line added; the text-slot sentences are paid by deleting the § Filter tokens lede (restated by its two bullets and the routing table) and the now-false `{{x}}` clause; no re-wrap paid a line; no ceiling moved (the ratchet script is outside the claimed surface).", "deviations": [ "PM assumption 3 is half-true: #19939 is open and pm:queue, but its C half (fields.* / assignments.* refuse `{…}`, CEL spelling prescribed; date macros and {$User.*} kept) landed in #22259 (0e9e7b70) — the skill's item 1 already matched it; value-slot guidance untouched.", "PM assumption 7: no LINE ratchet covers skills/** (check-skill-line-ratchet.mjs excludes the published catalog by design), but a TOKEN ratchet does (scripts/check-skills-token-ratchet.mjs, ceil(bytes/4), shrink-only): SKILL.md had 88 bytes of headroom and the eval 2 bytes, which — not the line count — bounded the rewrite.", "The dispatch's enumeration regex `\\{[^{]` also matches the second brace of a `{{ ` hole, so the ruling's 0-hit pin was read with a lookaround form (a `{` with no `{` on either side); positive control on the base: 2 hits.", "Edits beyond the three named sites, all inside the claimed file surface: § Filter tokens lines 207–208 (`title`, `message` dropped from the single-brace list), 213–214 (message body → a `{{ }}` hole), and the two-line section lede deleted to pay the token ratchet — flagged in the PR body for the maintainer.", "origin/main moved from 587bd969 (dispatch) to d87dff67 (base, fetched at start) and then to c512c255 during the run; the branch is based on d87dff67 and was not re-merged.", "Labels on the PR not set by this run: `documentation` and `size/s` (labeler / size-labeler); left alone." ], "files_changed": [ "skills/objectstack-automation/SKILL.md", "skills/objectstack-automation/evals/flows-triggers-approvals.json" ], "mcp_calls": "0 — none", "api_writes": "3 strokes / 4 REST writes, all through scripts/pm/ as objectstack-fleet[bot]: pr_create → POST /repos/objectstack-ai/objectstack/pulls (fleet-write relay run 37926484670, read back identical, 11468 bytes); label-write → POST /repos/objectstack-ai/objectstack/issues/22475/labels (skip-changeset) + POST .../issues/22475/assignees (os-elon-musk) (relay run 37926571692, read back ok); post-stamped → POST /repos/objectstack-ai/objectstack/issues/22454/comments (this report). git push ×2 (empty-branch probe, then 057ce436) — not REST.", "open_questions": [], "out_of_scope_findings": [ "class: none (read-only inference) · SKILL.md:154 `The handler reads {NODE_ID.error} (or run-wide {$error})` names no slot; in a text slot it is now `{{ n1.error }}` / `{{ $error.message }}`, in a value-like position still single-brace; the door names the spelling loudly · carrier: 承接者:无 — noted in Acceptance notes, not filed", "class: none (polish) · the eval's token ceiling is lowerable 1,255 → 1,253 in scripts/check-skills-token-ratchet.mjs, outside this card's surface · carrier: 承接者:无 — noted, not filed", "class: none (read-only inference, no named producer) · `{{ $User.Id }}` in a text slot renders blank with ok: true and passes objectstack validate (validate-flow-template-paths.ts skips an unresolvable root; lint-flow-patterns.ts reads a bare `$` only outside holes); the skill paragraph now warns · dedupe: text slot $User hole blank validate · carrier: 承接者:无 — noted, not filed" ] }Posted 2026-10-09T11:55Z by the dev subagent of
session_01JmWtcHfGbC4ncw4GFKWuRA; PR #22475 stays draft (Tier H).
Generated by Claude Code
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsACCEPT — PR #22475 (head
057ce436), Tier H, the four-piece set posted, landing on the maintainer's APPROVED — skills seat 1,session_01JmWtcHfGbC4ncw4GFKWuRA, 2026-10-09T12:01ZVerified on GitHub and on the fetched branch, not on the report: a draft PR against
main, first lineFixes #22454(the only closing keyword in the body), 2 files (+17/−18): the notify example nowtitle: 'Done: {{ record.title }}'/message: 'Closed {{ record.closed_at | date:long }}'(measured on the built dists: accepted byNotifyConfigSchema, rendered by the engine; the old lines refused with the door's remedies); the dialect paragraph three statements, one door each, the false{{x}}clause gone; eval id 2 expectstitle: 'Won: {{ record.name }}'with{{moved frommust_not_containtomust_contain; the § Filter tokens bullets no longer list text slots as single-brace positions and the section's lede (restated at lines 161–162 and in its own second bullet) deleted to pay the token ratchet. SKILL.md 439 → 438 lines; token ratchet 5,783 / 5,785 and 1,253 / 1,255, shrink-only, exit 0. Pin: a text-slot value holding a lone{— 0 hits acrossskills/on the head (2 on the base, the positive control). Gates: the dev's 24-family union pinned to057ce436(--ran24/24, all exit 0); CI on the head at this write: four jobs stillin_progress, nothing red — the landing waits for them and for the approval anyway.mcp_calls0;api_writes3 relay strokes (pr_create, label-write, the report 6080357455) —skip-changesetand the assignee read back. Commit authored byobjectstack-fleet[bot]. Deviations accepted as listed in the review record (two mechanism assumptions corrected by measurement; the edits beyond the three sites are inside the claimed file and paid). NOT MEASURED: the skill eval run (no runner in this repository reads the assertions).Contract review PASS in seat at tier: 6080468777. Landing: Tier H (
skills/**) — the four-piece set is posted in this act (this ACCEPT, the review record,needs-user-decision+ 维护者速读 on the PR, review requested fromos-zhuangandhotlongthrough the relay); on an authorized APPROVED the seat lands unless the approver already did; close-out is the seat's either way. Out-of-scope: the{{ $User.Id }}blank render in a text slot is filed for the spec lane (link follows on this card); the rest stays in Acceptance notes.objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsLanded — PR #22475 MERGED by the approver, card closed by
Fixes— skills seat 1,session_01JmWtcHfGbC4ncw4GFKWuRA, 2026-10-09T13:04ZTwo readings, taken together: PR #22475 reads
merged: trueat 2026-10-09T13:02Z (commit8b713fad783e23d4313fa7d291b88de0bde6efd7, merged through the merge queue afteros-zhuang's APPROVED 2026-10-09T12:24Z — review 5469966482 — and their own ready flip, arm (merge) and enqueue at 12:27Z; thirty-five minutes in the queue), and that commit is an ancestor oforigin/mainas fetched in this act — a single-parent squash commit: the merge queue squashes with its own method whatever the arm said (the approver's arm readmerge; shift 1's two-parent commit on PR #22382 was therefore not a queue merge of this kind) — the MERGED proof is by content, never by parent count. The two files are byte-identical to the PR head057ce436(git diff 057ce436 <merge commit> -- skills/objectstack-automation/SKILL.md …/evals/flows-triggers-approvals.jsonis empty); the published skill now teaches the{{ }}delimiter in the flow text slots, the lone-{text-slot pin reads 0 acrossskills/.Tier H landing by the maintainer's authorized approval on this seat's four-piece set (contract review PASS 6080468777, ACCEPT 6080477431, 维护者速读 6080482877, review requested from both authorized approvers). The card closed
completedbyFixes;pm:dispatchedand the assignee are stripped in this act, and so is the PR'sneeds-user-decision. Timings: claim → draft PR 35 min; four-piece set → APPROVED 22 min; APPROVED → MERGED 38 min. The out-of-scope finding is filed as #22477 (spec lane, the{{ $User.Id }}blank render). Nothing else is owed on this card.
Filing gate: ① governed text that a landed change made false. This is a Tier H follow-up, owed at #22110's landing. Filed by the
domain:specseat 2 (seat post #18549,session_01DhTqaEHqPVSVnAkjG3jywn), which landed PR #22315. ⛔ Not graded or routed here; ⛔ not a claim. The edit is toskills/**, so its PR takes the governed-surface endgame (a maintainer's approval).What changed on
mainPR #22315 (
2b61f2d9d,Fixes #22110, ADR-0032 §3) makes a flow's text slots read the{{ }}delimiter. A single-brace{token}in those slots is refused at the build doors and the save door, with the hole spelling as its remedy. The slots are the notify node'stitle/message, the screen node'stitle/description, and the end node'smessage. Value slots are untouched.What the skill still teaches (read at
2b61f2d9d)Every location below was named by the contract reviews on PR #22315 (
6076666309,6077952132):skills/objectstack-automation/SKILL.md:108–:109: the notify example,title: 'Done: {record.title}'andmessage: 'Closed by {$User.Id}'.skills/objectstack-automation/SKILL.md:236–:241: "Text slots (notifytitle/message,inputs,httpurl/body, …) andfilterkeep the SINGLE-brace template —{var}/{record.title}…". This is false for the notify (and screen / end) text slots.inputs,httpurl/body,filter) still holds is for the owner to check against the value-slot rules onmain, including [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 passes.skills/objectstack-automation/evals/flows-triggers-approvals.json, eval id 2 (:17): itsexpected_outputasks for a notify "titlewith single-brace interpolation".What the fix needs
{{ }}delimiter, as ADR-0032 §3 and PR feat(spec)!: flow text slots read the {{ }} delimiter, refusing a single-brace token with its hole spelling (#22110) #22315's changeset state it.mainnow enforces.Dedupe: REST search over open issues for "objectstack-automation SKILL single-brace" found 0. Related: #22110 (the change), #19939 (the value-slot dialect).
Generated by Claude Code