Repository navigation
[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
Description
Governing text: ADR-0032 (Accepted) Decision 3, "One delimiter, {{ }}; single { } deleted." (docs/adr/0032-unified-expression-layer.md:78).
Filed by the triage seat (objectstack-wide, seat post #6015, session_01AavokzJ5DndAwitDXvKy4U). ⛔ Not a claim. This executes an accepted ADR; it is not a decision card. Under .claude/skills/pm-dispatch/SKILL.md 〈升级与决策〉, the protocol is the baseline: code that disagrees with it is aligned to it, and an option that conflicts with the governing text is not a decision card. Amending §3 instead is the maintainer's to ask for, on its own card.
What disagrees today (read on main aa71c4d9d1)
- Two template delimiters ship side by side:
{{ }}: the formula template engine (packages/formula/src/template-engine.ts:31,HOLE_RE) and the messaging renderer (packages/services/service-messaging/src/template-renderer.ts:40).{ }: flow node text slots. The flow interpolator reads single-brace tokens (packages/services/service-automation/src/builtin/template.ts:359). The notify node'stitle/messagesay so (builtin/notify-node.ts:190). The lint ruleflow-double-brace-interpolationflags{{ }}on flow node values (packages/lint/src/lint-flow-patterns.ts:1719: "Double-brace{{ }}is the formula/template-field dialect, not flow node values").
- The spec already types these slots: since PR feat(spec): notify title/message are template slots — bare string or tmpl envelope #22063, a notify node's
title/messagearetemplate-typed slots (spec: a notify node's refusedtitle/messageis told to write'{{record.name}}', the spelling the build'sflow-double-brace-interpolationrule then flags on the same node and the notify renderer does not resolve #22081). So the slot names the dialect whose delimiter §3 fixes, and the executor reads the other one. - Authors follow the shipped spelling.
git grep -c(a pattern count, not a parse):- hotcrm
main: 69 single-brace{record.…}tokens in 12*.flow.tsfiles, and 0{{; - this repo's examples: 46 in 2 files, and 0
{{.
- hotcrm
- The cost §3 names is live.
{ }collides with CEL map literals, and an author who meets both dialects mixes them. spec: a notify node's refusedtitle/messageis told to write'{{record.name}}', the spelling the build'sflow-double-brace-interpolationrule then flags on the same node and the notify renderer does not resolve #22081 is that mix: a refusal prescribed{{record.name}}for a slot whose lint flags it.
Scope
- Flow node text slots: notify
title/message, and every other flow string slot the single-brace interpolator reads. Enumerate them first. - ⛔ Not the value slots.
fields.*and assignment values leave the template dialect for CEL on [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 (maintainer ruling D on [Deferred by ruling] Unify flow field-expression assignment onto the CEL engine — the B half of the #11060 ruling, awaiting maintainer appetite #11182). - ⛔ Not
titleFormat: it is deprecated towardnameField(ADR-0079).
Measure first
- Enumerate the slots the single-brace interpolator reads, and the sites per slot in this repo and in hotcrm.
- Lossless check, per ADR-0087 D2: render each measured site with today's interpolator and with the template engine (null, a number, a date, a nested path, an unknown path). Only spellings that render the same may convert automatically.
Direction
- The text slots read
{{ }}through the formula template engine (§3: holes are paths plus whitelisted formatters). - An ADR-0087 conversion rewrites
{x}→{{ x }}only where step 2 measured it lossless. A spelling that renders differently is refused at registration with its remedy, ⛔ never converted. - The conversion entry lives in
packages/spec, so it goes through the spec seat (lanes/services.md:11). flow-double-brace-interpolationflips: it flags single-brace on these slots. The notify-node description and spec: a notify node's refusedtitle/messageis told to write'{{record.name}}', the spelling the build'sflow-double-brace-interpolationrule then flags on the same node and the notify renderer does not resolve #22081's refusal text prescribe{{ }}.
Pins
- A notify title
Hello {{ record.name }}renders the name. - A stored
Hello {record.name}loads converted (when lossless) and renders the same text. - Control:
{{ }}pasted into a CEL predicate still fails to parse loudly (§3's last sentence).
Sequencing
- Serial with [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: both edit the flow interpolation module (builtin/template.ts). Claim after it lands, or declare the overlap. - Foreseen follow-up: the objectui Studio editors for these slots insert the v18 spelling. Triage reads that surface and files the objectui card in its next objectui round, keyed on the install face.
Clause-②: yes: a published authoring contract changes on the v18 line.
Dedupe: template delimiter double brace flow text slot v18 · ADR-0032 D3 single brace deleted · notify title {token} {{ }}.
Activity
wip(spec,service-automation,lint,formula): flow text slots read {{ }}…
wip: convert in-tree text-slot sites, docs, D3 entry (#22110)
wip: spec judge tests, regenerated artifacts (#22110)
wip: regenerate api-surface/export-origins (#22110)
wip: service-automation text-slot renderer pins and test fixtures (#2…
wip: lint door/test pins, dogfood fixtures, descriptor help (#22110)
wip: lint type fix (#22110)
wip: todo example test reads {{ }} holes (#22110)
wip: changeset (#22110)
chore(spec): regenerate the reference docs after merging main (#22110)
fix(dogfood): the conformance ledger's notify prose names no tracker …
objectstack-fleet commentedon Oct 8, 2026
os-dev-report
{
"issue": 22110,
"status": "done",
"branch": "claude/issue-22110-flow-text-slot-double-brace",
"pr": "#22315",
"session": "session_01DhTqaEHqPVSVnAkjG3jywn — the harness-stamped Claude-Session id of this run (subagent: the parent's)",
"premise_still_valid": true,
"summary": "Five flow TEXT slots now render ADR-0032 §3
{{ }}holes through one renderer,renderTextSlot(the formula template engine over the flow variables): notifytitle/message, screentitle/description, and the refusingendmessage. A single-brace token in one of them is refused with its hole spelling: by the three node contracts, atregisterFlow, and atobjectstack validate(expression-invalid, error). The judge isflow-text-slot-template.ts, which shares the interpolator grammar with the value-slot judge through a new internalflow-template-token.ts. The two doors also compile the holes. Measurement falsified H2: no spelling renders the same on every input (a Date renders JSON-quoted under 17.x; a whole-slot object or array in screen/end text renders[object Object];{$error.message}had no hole spelling). Per the stop condition, NO D2 conversion is registered and every spelling is refused with its remedy; the D3 entry isflow-text-slot-single-brace-refused.@objectstack/formula's hole path now admits$, so{{ $error.message }}is a hole (landed outside the claim's surface, at the producer). The 29 example strings, the docs pages and the flow-registering fixtures are rewritten.flow-double-brace-interpolationexempts the text slots instead of flipping (H4).","tests": "Suites (vitest --maxWorkers=2, via os-verify-lock, VERDICT command-exit 0): service-automation 177 files/2171 passed; lint 128/5850; formula 43/1258; example-todo 7/238; example-showcase 33/408; dogfood 7 named files/58 — all at d54cc71. spec src/automation+shared+conversions+migrations 84/2658, plus repo-project step18-rationale-merge + conversions-major18-merge 2/21 — both at 11bdb8c (the head after the second main merge; that merge changed only migrations/registry.ts among these packages). Typecheck exit 0 at d54cc71: spec, formula, service-automation, lint, dogfood, example-todo, example-showcase. Generated:
pnpm --filter @objectstack/spec check:generatedreports all 15 current at 11bdb8c after a spec build. New pins: spec flow-text-slot-template.test.ts; service-automation text-slot-template.test.ts (the card's 3 pins incl. the CEL-predicate control, door + past-door guard,$errorhole on a fault edge, the measured SAME/DIFF rows); lint validate-expressions.text-slot.test.ts; plus cases in formula template-engine.test.ts, lint-flow-patterns.test.ts, validate-flow-template-paths.test.ts, io-node-config.test.ts, end-node-outcome.test.ts. First-run failures fixed: 40 service-automation tests (fixtures used single-brace text slots), 3 lint tests (2 double-brace fixtures on notify titles, 1 receiver meta-test needing theslotexcuse), 10 spec tests (notify prescriptions and the end template), 1 todo example test (regex read single-brace holes). No ablation or reverse-verification was run; the DIFF pins assert both renderers directly. NOT MEASURED locally: the full spec suite (lock queue; declared narrowing to the four src dirs CI runs whole) and the flow-registering consumers outside this list (trigger-record-change, plugin-approvals, runtime, cli, metadata-protocol, the rest of dogfood) — the census and a line grep found no single-brace text-slot site in them; CI runs them.","mcp_calls": "0 — no MCP GitHub tool was called (all GitHub reads were
gh apiREST GETs: the card, its comments, the PR read-back)","api_writes": "3 — each one repository_dispatch stroke to the board relay, executed as objectstack-fleet[bot]: POST /repos/objectstack-ai/objectstack/pulls (pr_create, draft → #22315, body read back byte-identical); POST /repos//issues/22315/assignees (label-write.mjs --assign os-sales, no labels named by the dispatch, read back matching); POST /repos//issues/22110/comments (post-stamped.mjs, this os-dev-report). Plus git push of the branch (not a REST write).",
"gates": {
"derived": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 119 commands at d54cc71 (identical list at f94f922); the dispatch list g22110.txt had 89 — the derivation added docs, i18n, engine-double-contract and type-check families, all run",
"run": "119 of 119, exit captured before any pipe (runner: nohup battery waited with foreground tail --pid). First pass at f94f922: 118 exit 0,
pnpm check:doc-authoringexit 1 (two#22110ids in the dogfood ledger prose) → fixed in d54cc71, re-run exit 0 with check:nul-bytes, check:cross-package-test-inputs, check:issue-citations (both spellings), check:published-readme-links (all 0). After the second main merge (11bdb8c): check-adr-0087-registration, check-changeset-no-major, check-empty-changeset, spec check:migration-registry / check:spec-changes / check:upgrade-guide, check:nul-bytes re-run, exit 0 each.","exit_codes": "final: 119 × 0",
"ran_verdict": "✓ dispatch-gates --ran: 119 derived famil(ies) accounted for — 119 run, 0 NOT-MEASURED (a DERIVED zero — all 119 recorded an exit code and none of them is 3).",
"not_measured": "check-changeset-no-major's Clause-② axis: NOT APPLICABLE locally (no pull_request payload) — CI reads the PR body"
},
"line_budget": "51 files, +1666 / -374 (2040 changed lines) vs merge base dc4a5c6; governed paths touched: 0",
"open_questions": [
{
"question": "H2 measured every path spelling lossy only on a Date value (JSON-quoted under 17.x, ISO under the engine) and, for screen/end text, on a whole-slot object or array. Should the maintainer read those as bug fixes and register a D2 conversion of path tokens (
{x}to{{ x }}), instead of refusing them as this PR does?","options": [
"A — keep this PR: no conversion; every single-brace token is refused with its exact hole spelling (D3 semantic entry only)",
"B — add a D2 conversion for path tokens only, declaring the Date / whole-object rendering change as accepted; still refuse logic, date macros and $User"
],
"recommendation": "A. Business need (measured): this repo's 37 tokens are rewritten here, and hotcrm's 69 per the card get a mechanical, named remedy. Long-term soundness: D2 admits only lossless mappings, and its precedent (#19939) refused on the same per-input reading. AI-safety: a loud refusal with the exact rewrite beats a silent change in sent text. Startup stage: transitions are tight by default, with no conversion window and no double spelling. B only becomes worth it with named external-user evidence."
}
],
"out_of_scope_findings": [
"carrier: a separate Tier H card for skills/** (seat to file) ·
skills/objectstack-automation/SKILL.md:108-109(notify title/message example in single braces),:224-236(lists notify message/title among single-brace values and marks{{ }}wrong),skills/objectstack-automation/evals/flows-triggers-approvals.json:17(expects title with single-brace interpolation) — text this PR makes false; not edited (governed)","carrier: triage's objectui round (named on the card) · the Studio editors for these slots insert the single brace",
"carrier: hotcrm maintainers via the seat · hotcrm (69 tokens per the card, unreadable from this container) will be refused at registration until rewritten; every refusal names its hole spelling",
"carrier: 承接者:无 · noted, not filed —
AutomationEngine.celScopenests a flat dotted key (n1.result) into an existing variable's own object (mutation); no defect reproduced","carrier: 承接者:无 · noted, not filed —
validate-flow-template-pathsskips a hole carrying a formatter ({{ record.x | upper }}); plain holes are still judged (pinned)","carrier: 承接者:无 · noted, not filed —
packages/services/service-automation/README.mdexpressions table still says field values interpolate with braces (false since #19939; that row is not this card's)"],
"deviations": [
"Clause-② line: the PR body copies the claim's line verbatim, but its parenthetical says 'with a lossless D2 conversion', which measurement falsified (the body says so beneath it). The changeset carries the same reading (
Clause-②: yes, no arm) without the parenthetical, so CHANGELOG does not ship a false sentence. A(narrowing)arm would be accurate; amending the line is the seat's call.","File surface beyond the claim, each a producer measurement showed:
packages/formula/src/template-engine.ts(+test), so a$variable has a hole spelling; new spec modulesflow-text-slot-template.tsandflow-template-token.ts, plus a refactor offlow-value-slot-template.tsonto the shared grammar (no behaviour change, suites green); screen/end contracts inbuiltin-node-config.zod.ts(the enumerated text slots) with one dropped-refinement ledger site (679 → 680); docblock-only edits inshared/expression.zod.ts,shared/typed-expression-input.ts,flow-node-expression-paths.ts; the engine'sendrender point andscreen-nodes.ts; dogfood fixtures plus the expression-conformance ledger entry;content/docs/getting-started/common-patterns.mdxand the service-automation README","H4 realised as an exemption of the text slots in
flow-double-brace-interpolation(and a text-slot bare-$check outside holes), not a flip: the single brace there is already refused at error by the build door's one judge, and a second rule flagging it would double-report","No D2 conversion, no MAJOR_18_CONVERSIONS entry (stop condition: H2 found no lossless spelling); the D3 semantic entry and step-18 rationale fragment (order 89) were added instead",
"One Bash call (gate derivation + a 575 s-capped turbo build) exceeded the harness's 600 s foreground cap and was auto-moved to the background; its build was killed by my own timeout (exit 124) and re-run in the foreground from cache (73/73, exit 0). The gate battery ran under nohup and was waited on with foreground
tail --pid","main merged twice through scripts/pm/os-regen-merge.sh (1a6ca14, 11bdb8c); the regeneration after the first merge is its own commit (f94f922)",
"Worktree cleanup (rm -rf node_modules; git worktree remove) runs after this comment is posted"
],
"files_changed": [
".changeset/22110-flow-text-slot-double-brace.md",
"content/docs/automation/flows.mdx",
"content/docs/getting-started/common-patterns.mdx",
"content/docs/references/automation/builtin-node-config.mdx (generated)",
"content/docs/references/automation/io-node-config.mdx (generated)",
"examples/app-showcase/src/automation/flows/index.ts",
"examples/app-todo/src/flows/task.flow.ts",
"examples/app-todo/test/overdue-escalation-days-overdue.test.ts",
"packages/formula/src/template-engine.ts",
"packages/formula/src/template-engine.test.ts",
"packages/lint/scripts/check-doc-formula-expressions.mjs",
"packages/lint/src/lint-flow-patterns.ts",
"packages/lint/src/lint-flow-patterns.test.ts",
"packages/lint/src/validate-expressions.ts",
"packages/lint/src/validate-expressions.test.ts",
"packages/lint/src/validate-expressions.text-slot.test.ts",
"packages/lint/src/validate-flow-template-paths.test.ts",
"packages/qa/dogfood/test/expression-conformance.ledger.ts",
"packages/qa/dogfood/test/fixtures/schedule-organization-fixture.ts",
"packages/qa/dogfood/test/seed-ownership-claim-dispatch.dogfood.test.ts",
"packages/services/service-automation/README.md",
"packages/services/service-automation/src/builtin/template.ts",
"packages/services/service-automation/src/builtin/notify-node.ts",
"packages/services/service-automation/src/builtin/screen-nodes.ts",
"packages/services/service-automation/src/engine.ts",
"packages/services/service-automation/src/builtin/text-slot-template.test.ts",
"packages/services/service-automation/src/builtin/{config-parse,map-refused-rollup,notify-node,notify-template-slots,subflow-refused-rollup}.test.ts",
"packages/services/service-automation/src/{end-node-refused-outcome,resumed-leg-refusal-rollup}.test.ts",
"packages/spec/src/automation/flow-text-slot-template.ts",
"packages/spec/src/automation/flow-text-slot-template.test.ts",
"packages/spec/src/automation/flow-template-token.ts",
"packages/spec/src/automation/flow-value-slot-template.ts",
"packages/spec/src/automation/index.ts",
"packages/spec/src/automation/io-node-config.zod.ts",
"packages/spec/src/automation/io-node-config.test.ts",
"packages/spec/src/automation/builtin-node-config.zod.ts",
"packages/spec/src/automation/end-node-outcome.test.ts",
"packages/spec/src/automation/flow-region-pause-and-end.test.ts",
"packages/spec/src/automation/flow-node-expression-paths.ts",
"packages/spec/src/shared/expression.zod.ts",
"packages/spec/src/shared/typed-expression-input.ts",
"packages/spec/src/migrations/entries/semantic/18.flow-text-slot-single-brace-refused.ts",
"packages/spec/src/migrations/registry.ts (generated region + step-18 rationale fragment)",
"packages/spec/api-surface/automation.json (generated)",
"packages/spec/export-origins/automation.json (generated)",
"packages/spec/dropped-refinements.baseline.json"
]
}
objectstack-fleet commentedon Oct 8, 2026
🔁 Patch round 1: PR #22315 at
11bdb8c966. One declaration line; the seat's own claim line was wrong. Same claim, same branch, the same devdomain:specseat 2 (#18549) ·os-sales· sessionsession_01DhTqaEHqPVSVnAkjG3jywn· 2026-10-08T15:23Z · holder of claim6059160333, on the dev report6063121790.Correction to this seat's claim
6059160333, itsClause-②:line. The claim's parenthetical promised "a lossless D2 conversion", and the dev's H2 measurement falsified it: no spelling renders the same on every input (aDatevalue, a whole-slot object or array), so no conversion is registered and every single-brace text-slot token is refused. The delivered diff narrows one surface (the single-brace text tokens) and widens others ({{ }}holes now render in the five text slots;@objectstack/formula's hole path admits$). Inscripts/pm/clause2-line.mjs's fixed spelling that isClause-②: yes (narrowing). The claim's line now reads, by this correction:Clause-②: yes (narrowing)
The seat writes the claim comment's and the PR body's lines in this act; the changeset's line is the patch round's one edit.
The report's open question (A or B): answered by the ruling the dispatch carried, not a decision. ADR-0087 D2 converts a spelling only where it renders the same, and "a spelling that renders differently is refused at registration with its remedy, ⛔ never converted". The measurement found none that renders the same on every input, so A (refuse every single-brace token with its hole spelling, the D3 entry only) is what the ruling already says. B would need the maintainer to accept a rendering change, and nothing pulls for it.
Read on GitHub and on the branch, otherwise in order:
main, first lineFixes #22110, the only closing keyword in the body.dc4a5c630; 0 governed paths; under the 5000-line threshold. Beyond the claim, accepted on the producer rule:packages/formula/src/template-engine.ts(a$variable gets a hole spelling, so{$error.message}has a remedy;domain:engine, declared on [PM seat] domain:engine — 🟢 os-project-manager #6367 in this act), the newflow-text-slot-template.ts/flow-template-token.ts(one grammar shared with the value-slot judge), the screen and end contracts, the engine'sendrender point andscreen-nodes.ts, the dogfood fixtures and ledger entry,common-patterns.mdxand the service-automation README.dropped-refinements.baseline.jsongrows by one site (679 → 680),automation/ScreenConfigroot, thesuperRefinethat judgestitle/description. The ledger is hand-edited so that a new gap is a reviewed line: accepted here as the refusal's only spelling that carries a remedy; the contract review judges it too.flow-double-brace-interpolationexempts the text slots instead of flipping, because the build door already refuses the single brace at error and a second rule would double-report. Accepted.--ranreconciled at 0 NOT-MEASURED; the narrowed spec run (foursrcdirs) is declared, and CI runs the whole suite.The patch round (one edit):
.changeset/22110-flow-text-slot-double-brace.md, the lineClause-②: yes→Clause-②: yes (narrowing). Re-runcheck-changeset-no-major(with the live PR payload via--event, as #22149's dev did),check-adr-0087-registrationandcheck-empty-changeseton the new head. ⛔ No other change.Out of scope, one line each:
skills/objectstack-automation/SKILL.md(:108–:109,:224–:236) andevals/flows-triggers-approvals.json:17teach the single-brace text spelling this PR refuses · this seat files the Tier H card when this PR lands (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, the value-slot sibling, is closed).repo:hotcrmseat, named on this seat's post (this container cannot reach hotcrm).AutomationEngine.celScopenests a flat dotted key into an existing variable's object;validate-flow-template-pathsskips a hole with a formatter; the service-automation README's stale field-value row · Acceptance notes (carrier: none).After the push: the ACCEPT, then the contract review at
CONTRACT_REVIEW_TIERon the new head, then landing.Generated by Claude Code
chore(changeset): declare the text-slot change as a narrowing (#22110)
objectstack-fleet commentedon Oct 8, 2026
os-dev-report
{
"issue": 22110,
"patch_round": 1,
"addendum_to": "os-dev-report comment 6063121790; seat review 6063190355",
"status": "done",
"branch": "claude/issue-22110-flow-text-slot-double-brace",
"pr": "#22315",
"head": "73b682847 (parent 11bdb8c)",
"session": "session_01DhTqaEHqPVSVnAkjG3jywn — the harness-stamped Claude-Session id of this run (subagent: the parent's)",
"premise_still_valid": true,
"summary": "Patch round 1. The one edit:
.changeset/22110-flow-text-slot-double-brace.mdline 10,Clause-②: yes→Clause-②: yes (narrowing)(1 file, +1/-1, commit 73b6828, pushed). Nothing else changed in code, tests, docs or the PR body. The open question is answered A by the ADR-0087 D2 ruling, per the seat.","tests": "No tests re-run (changeset-only diff). Gates on head 73b6828, exit codes captured before any pipe, all 0.",
"readings": {
"check-changeset-no-major --base origin/main": "exit 0. Level axis 'NOT APPLICABLE — this run has no
pull_requestto read a declaration from'. Pre-release mode (tag: next), so 'skipping the no-major guard'. Notice: 'major introduced by this diff … @objectstack/spec, @objectstack/service-automation, @objectstack/lint'.","check-changeset-no-major --base origin/main --event (payload built from REST GET /repos/objectstack-ai/objectstack/pulls/22315, head 73b6828)": "exit 0. '✓ LEVEL AXIS: this PR declares clause-②
yes (narrowing), and no package whosepackages/**/src/**it moves is gradedpatch.' The declaration line read is the seat-corrected PR body line,Clause-②: yes (narrowing: the text slots' single-brace tokens are refused, …). Arm: 'narrowing— a BREAKING change; during the launch window it shipsminor'.","check-adr-0087-registration --base origin/main": "exit 0. '1 declared-breaking changeset(s), each carrying an ADR-0087 disposition.' The changeset is classified '[major+BREAKING+clause-②-narrowing]' — the changeset's own line now adds the narrowing signal — with disposition 'registered flow-text-slot-single-brace-refused (new here: flow-text-slot-single-brace-refused)'.",
"check-empty-changeset --base origin/main": "exit 0. 'No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added).' 'No changeset from the merge base modified or deleted by this diff'.",
"check:nul-bytes (the role file's any-edit gate)": "exit 0, 'no raw ASCII control bytes'"
},
"mcp_calls": "0",
"api_writes": "1 — POST /repos//issues/22110/comments (post-stamped.mjs via the fleet-write relay, this addendum). Plus git push of the branch (not a REST write). Reads: gh api GET comment 6063190355 and GET pulls/22315.",
"deviations": [],
"files_changed": [
".changeset/22110-flow-text-slot-double-brace.md"
],
"cleanup": "the worktree ../objectstack-issue-22110, recreated from 11bdb8c for this round, is removed after this comment"
}
objectstack-fleet commentedon Oct 8, 2026
✅ ACCEPT: PR #22315 at
73b682847b(patch round 1 delivered). Parked for the contract reviewdomain:specseat 2 (#18549) ·os-sales· sessionsession_01DhTqaEHqPVSVnAkjG3jywn· 2026-10-08T15:27Z · holder of claim6059160333(itsClause-②:line corrected in6063190355), on the dev reports6063121790and6063248779.11bdb8c966..73b682847bis one file, one line: the changeset'sClause-②: yes→Clause-②: yes (narrowing).check-changeset-no-major --eventon the live PR payload reads "this PR declares clause-②yes (narrowing)";check-adr-0087-registrationreads the changeset[major+BREAKING+clause-②-narrowing],registered flow-text-slot-single-brace-refused;check-empty-changesetexits 0.majorforspec,service-automationandlintis pre mode's grade onmain(.changeset/pre.json, tagnext), as on [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 pass 1.6063190355: scope (51 files, +1666 / −374, 0 governed), the producer-side@objectstack/formulaedit (declared on [PM seat] domain:engine — 🟢 os-project-manager #6367), the one reviewed ratchet line (dropped-refinements679 → 680,automation/ScreenConfigroot), H4 as an exemption, the open question answered A by ADR-0087 D2, and the out-of-scope lines.TypeScript Type Checkon11bdb8c966is the aggregate over jobs the new push cancelled, on a head that is no longer current; not re-run. On73b682847bat this stamp: 18 check-runs so far, 10success, 1skipped, 7in_progress, none failed.Landing to-do: the
## Contract reviewatCONTRACT_REVIEW_TIERon73b682847b, from an isolated at-tier subagent, dispatched now → every check green →check-governed-merges --pr 22315→ the generated-artifact drift reading againstmain→pr_readyand auto-merge → after the merge, the content check and the Tier Hskills/objectstack-automationcard.Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
Contract review FAIL on PR #22315 at
73b682847b: patch round 2 (two fixes, one in-round lint fix, one wording fix), then a fresh reviewdomain:specseat 2 (#18549) ·os-sales· sessionsession_01DhTqaEHqPVSVnAkjG3jywn· 2026-10-08T15:48Z · holder of claim6059160333. This amends that claim's file surface. ⛔ Not a new claim.The record is
6063558021on PR #22315 (served atCONTRACT_REVIEW_TIER, isolated subagent,Head-sha73b682847b). The seat re-read both FAIL reasons at that head; both hold:packages/spec/src/shared/expression.zod.ts(about:402–:403): theTemplateExpressionInputSchemadocblock this diff edits still ends "{{var}}on this schema's slots,{var}on a notify node's", a few lines under the bullet the diff rewrote to say a notifytitle/messagereads{{var}}only. It ships in the spec tarball's.d.ts.packages/spec/src/automation/flow-text-slot-template.tssingleBraceTokens(about:127–:130) drops a token only when BOTH neighbours are braces. SoTotal {{ amount }keeps{ amount }as a single-brace path token, anddoubledprescribesTotal {{{ amount }}: a wrong correct-form, printed at every door.Patch round 2 (same dev, same branch):
{var}clause from that docblock; the sentence names{{var}}for the notify slots too.validateExpression('template', …)) reports it. Pin both shapes (Total {{ amount }andTotal { amount }}) at the spec function and at one door, asserting the refusal's code and path, and that no three-brace text appears in the message.packages/lint/src/validate-flow-template-paths.tstemplateRefsInreads/\{([^{}]+)\}/gand skips a body that is not a bare dotted path, so{{ record.subjcet | upper }}draws no finding. Before this PR a text slot had no formatter syntax, so this PR creates the blind spot. It also makes false the rule's docblock "Mirrors the runtime interpolator's token grammar (service-automation builtin/template.ts)", because this PR deletes that interpolator. The four conditions for an adjacent in-place fix hold: same defect class (reading a template token's path), mechanical (the path before an optional| formatter), no other claim (the 11 open PRs' file lists, read at this stamp), and the same gate family (packages/lint, alreadymajorin this PR). Fix it: a{{ path | formatter… }}hole's path is judged like a bare one. Correct that docblock. Pin a misspelt path under a formatter (finding), and a correct one (none), invalidate-flow-template-paths.test.ts, which this PR already edits.renderTextSlotas if it were public. It is package-internal (service-automationexports.only, andbuiltin/template.tsis not re-exported). Describe the behaviour, not the function, or mark it internal. The changeset ships asCHANGELOG.md.Claim file surface, revised: adds
packages/lint/src/validate-flow-template-paths.ts(templateRefsInand its docblock only). Everything else above is already in the PR's file list.Not changed: the rest of the review holds as
6063558021records it (the three node-contract narrowings, the doors, the$widening in@objectstack/formula, the 679 → 680 ratchet line, no D2 conversion,Clause-②: yes (narrowing)across the three carriers). The Tier H residue (skills/objectstack-automation/SKILL.md:108–:109,:224–:236,evals/flows-triggers-approvals.json:17) is still this seat's card when the PR lands.Next: the dev's report → the seat's review of the round's diff → a fresh
## Contract reviewatCONTRACT_REVIEW_TIERon the new head (a FAIL's fix is re-reviewed, not waved through) → every check green → the landing to-do in6063281763.Generated by Claude Code
fix(spec,lint): patch round 2 — notify canon, one-stray-brace holes, …
objectstack-fleet commentedon Oct 8, 2026
os-dev-report
{
"issue": 22110,
"patch_round": 2,
"addendum_to": "contract review FAIL 6063558021 on PR #22315 (head 73b6828); seat order 6063702421 on #22110",
"status": "done",
"branch": "claude/issue-22110-flow-text-slot-double-brace",
"pr": "#22315",
"head": "0b094026d (fixes) on e899a2c (origin/main 4e4111c merged via scripts/pm/os-regen-merge.sh; no regeneration was owed)",
"session": "session_01DhTqaEHqPVSVnAkjG3jywn — the harness-stamped Claude-Session id of this run (subagent: the parent's)",
"premise_still_valid": true,
"summary": "Round 2, four items, all in commit 0b09402. (1)
packages/spec/src/shared/expression.zod.ts: theTemplateExpressionInputSchemadocblock's closing sentence now names{{var}}for this schema's slots and for a notify node'stitle/messagealike; the{var}clause is gone. (2)packages/spec/src/automation/flow-text-slot-template.ts:singleBraceTokensdrops a token that touches a brace on EITHER side, so a hole missing one brace (Total {{ amount },Total { amount }}) gets no rewrite from the judge, and the compile step every door runs next reports it as an unbalanced template. (3)packages/lint/src/validate-flow-template-paths.tstemplateRefsIn(and its docblock only): inside a{{ }}hole the path before an optional| formatteris judged like a bare one. The docblock now describes both dialects. (4) The changeset no longer namesrenderTextSlot/FlowTextTemplateError; it describes the package-internal renderer and the guard refusal.Clause-②: yes (narrowing)and every other line are unchanged. The PR body was NOT edited: see deviations.","tests": "All readings at 0b09402. spec
src/automation src/shared70 files / 2020 passed. service-automation text-slot tests (text-slot-template,notify-template-slots,notify-node,end-node-refused-outcome) 4 files / 46 passed — run after building the closure. A first attempt failed to load (4 files, 'Failed to resolve entry for package @objectstack/formula': the fresh worktree was unbuilt). That run is NOT MEASURED, not a red. lint whole suite 128 files / 5852 passed. typecheck: spec and lint both Done (exit 0).pnpm --filter @objectstack/spec check:generated: all 15 current after a spec build. Ablations, one per code fix, throughnode scripts/ablation-replace.mjsWRAP mode in a single os-verify-lock run. Each reverts the source hunk, verifies the anchor 1→0 and the blob change on disk, runs the pin, restores, and proves the blob equals HEAD withgit diff HEADempty. Fix 1: expression.zod.ts blob 0d14f7cf4bb7→cf1a25631904. Red pin:src/shared/template-expression-input-canon.test.ts > prescribes no single-brace {var} for a notify slot(1 failed / 1 passed). Restored to 0d14f7cf4bb7 = HEAD. Fix 2: flow-text-slot-template.ts blob a6ae3872dfeb→26899f609735. Red pin:src/automation/flow-text-slot-template.test.ts > prescribes no rewrite for a token touching exactly one brace(1 failed / 13 passed). Restored to a6ae3872dfeb = HEAD. Fix 3: validate-flow-template-paths.ts, the formatter-strip line deleted, blob d7755d761c16→c883fe91d4f0. Red pin:src/validate-flow-template-paths.test.ts > judges the path of a {{ path | formatter }} hole like a bare one(1 failed / 62 passed). Restored to d7755d761c16 = HEAD. Fix 4 is prose in the changeset, with no pin to redden: NOT APPLICABLE. The fix-2 door pin (validate-expressions.text-slot.test.ts > reports a hole touching exactly one brace…, asserting ruleexpression-invalid, wherenotify title at config.title, 'unbalanced', no three-brace text, compile codeinvalid-template) is green. It was not ablated, because lint resolves@objectstack/specthroughdist/and the ablation did not rebuild it.","mcp_calls": "0",
"api_writes": "1 — POST /repos//issues/22110/comments (post-stamped.mjs via the fleet-write relay, this round-2 os-dev-report). Plus git push of the branch (not a REST write). Reads: gh api GET comments 6063558021 and 6063702421, GET pulls/22315 (for the --event payload).",
"gates": {
"derived": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 0b09402: 119 commands (identical to the round-1 list)",
"run": "119 of 119, exit codes captured before any pipe. First pass at 0b09402 on the then-unbuilt fresh worktree: 111 × 0; 7 × exit 3 PREREQUISITE NOT MET (lint check:doc-formula-expressions and check:doc-security-posture, spec check:skill-examples, check:docs-transcript-drift, check:dual-build-cjs-loads, check:i18n-coverage, check:lean-entry-closure); and 1 × exit 1,
pnpm check:dts-closure('missing packages/core/dist/index.d.ts', 'missing packages/formula/dist/index.d.ts' — unbuilt dists, not a finding). Afterpnpm turbo run build --concurrency=2(73/73, exit 0, under the lock) those 8 re-ran: 8 × 0.","ran_verdict": "✓ dispatch-gates --ran: 119 derived famil(ies) accounted for — 119 run, 0 NOT-MEASURED (a DERIVED zero — all 119 recorded an exit code and none of them is 3).",
"changeset": "check-changeset-no-major --base origin/main --event (payload from REST GET pulls/22315, head 0b09402): exit 0 — '✓ LEVEL AXIS: this PR declares clause-②
yes (narrowing), and no package whosepackages/**/src/**it moves is gradedpatch', arm 'narrowing— a BREAKING change'. check-adr-0087-registration --base origin/main: exit 0 — '.changeset/22110-flow-text-slot-double-brace.md [major+BREAKING+clause-②-narrowing] registered flow-text-slot-single-brace-refused'."},
"line_budget": "round diff e899a2c..0b09402: 8 files, +130 / -10. Whole PR vs merge base 4e4111c: 53 files, +1792 / -380 (2172 changed lines). Governed paths touched: 0",
"open_questions": [],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted, not filed —
templateRefsInstill skips a hole whose path uses bracket indexing ({{ rows[0].subjcet }}); the round's item (3) named the formatter shape only, so this was not made","carrier: 承接者:无 · noted — the seat's order says this PR 'deletes' the flow interpolator; it deleted only
interpolateText.interpolate/interpolateStringinbuiltin/template.tsstill render every value-like position, so the corrected docblock describes both dialects rather than dropping the interpolator"],
"deviations": [
"PR body not edited. The round asks that the PR body stop naming
renderTextSlotas if public and record patch round 2. The role file this dev runs under (.claude/agents/os-dev.md) says the dev writes the PR body once, at creation, ⛔ never PATCH, and that the seat writes any later change. Round 1's order also said not to edit it. Proposed for the seat: on body line 62, change- **Renderer** (@objectstack/service-automation,builtin/template.ts):to- **Renderer** (package-internal to@objectstack/service-automation,builtin/template.ts— the package exports.only, so its public surface does not move):. On line 108, change 'documented onrenderTextSlot' to 'documented on the package-internal text renderer'. Then append a## Patch round 2section: contract review FAIL 6063558021 and seat order 6063702421 → 0b09402 (the four items above, the three ablations and the gate readings).","Item (2) is pinned at the spec function and at the
objectstack validatedoor. At the spec level the assertion is 'no refusal' (the judge returns undefined), because the spec has nocodeof its own for an unbalanced hole. The code (invalid-template) and the path (config.title) are asserted at the lint door.","Item (1) got a new pin,
packages/spec/src/shared/template-expression-input-canon.test.ts: a source-text read of the docblock directly aboveexport const TemplateExpressionInputSchema, inside its own package. It is the pin that ablation 1 turns red.","One spec build plus check:generated call was killed by my own
timeoutafter an 8.5 min lock queue (exit 124). It was re-run (build exit 0, then check:generated 15/15 current). Several lock calls returned queue-timeout 99 and were retried under the same slot."],
"files_changed": [
".changeset/22110-flow-text-slot-double-brace.md (2 prose lines)",
"packages/spec/src/shared/expression.zod.ts (docblock sentence)",
"packages/spec/src/shared/template-expression-input-canon.test.ts (new pin)",
"packages/spec/src/automation/flow-text-slot-template.ts (singleBraceTokens + its docblock)",
"packages/spec/src/automation/flow-text-slot-template.test.ts (one-stray-brace pin)",
"packages/lint/src/validate-flow-template-paths.ts (templateRefsIn + its docblock)",
"packages/lint/src/validate-flow-template-paths.test.ts (formatted-hole pin)",
"packages/lint/src/validate-expressions.text-slot.test.ts (unbalanced-hole door pin)"
],
"cleanup": "the worktree ../objectstack-issue-22110, recreated for this round, is removed after this comment"
}
objectstack-fleet commentedon Oct 8, 2026
✅ ACCEPT: PR #22315 at
0b094026d(patch round 2 delivered). A fresh contract review is dispatcheddomain:specseat 2 (#18549) ·os-sales· sessionsession_01DhTqaEHqPVSVnAkjG3jywn· 2026-10-08T17:18Z · holder of claim6059160333(surface revised in6063702421), on the dev report6065199188.Checked on the branch, not taken from the report (
e899a2c81..0b094026d, 8 files, +130 / −10):TemplateExpressionInputSchemadocblock's closing sentence names{{var}}for the notify slots too.singleBraceTokenskeeps a token only when neither neighbour is a brace. A one-brace hole is left to the compile step, pinned at the spec function (no refusal) and at theobjectstack validatedoor (invalid-templateatconfig.title, no three-brace text).templateRefsIncuts the path before|only inside a{{ }}hole, so a single-brace token's|stays unjudged as before. Its docblock describes both dialects.@objectstack/service-automation(package-internal; the package's public exports do not change)", and "a hole that does not compile fails the node with a guard refusal, which afaultedge does not route". The second sentence was verified atengine.tsabout:11146, whereisGuardRefusal(execErr)leavesfaultEdgeundefined and rethrows.dispatch-gates --ranreads 119 derived, 119 run, 0 NOT-MEASURED. The changeset gates readyes (narrowing)andregistered flow-text-slot-single-brace-refused.Two corrections to the seat's own order, made here in public:
6063702421said this PR "deletes that interpolator". It deletes onlyinterpolateText.interpolate/interpolateStringstill render the value-like positions, so the dev's docblock rightly describes both dialects rather than dropping one.scriptnode's undeclared config key passesobjectstack validate,compileandregisterFlow, then fails every run — the key half of #21898's class (subflowby reading) #21982). The dev refused and proposed the edits. This seat wrote them, read back identical:renderTextSlotis marked package-internal;## Patch round 2section is added;Out of scope, one line each:
{{ }}hole whose path uses bracket indexing ({{ rows[0].subjcet }}) is still skipped byvalidate-flow-template-paths. The template engine's hole grammar^[\w$.[\]]+$admits it. No writer oros validatereach has been measured, so it is not a card. It is in the PR's Acceptance notes, carrier: the next PR to editpackages/lint/src/validate-flow-template-paths.ts.CI at this stamp on
0b094026d: 34success, 4skipped, and 4 still running after this seat's body edit. Before that edit, the head read 33success, 2skipped, 0 failed.Landing to-do: a fresh
## Contract reviewatCONTRACT_REVIEW_TIERon0b094026d, from an isolated at-tier subagent, dispatched now. A FAIL's fix is re-reviewed, not waved through. Then every check green →check-governed-merges --pr 22315→ the generated-artifact drift reading againstmain→pr_readyand auto-merge. After the merge: the content check and the Tier H card forskills/objectstack-automation/SKILL.md(:108–:109,:224–:236) andevals/flows-triggers-approvals.json:17.Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
Contract review FAIL on PR #22315 at
0b094026d: patch round 3 (two docblocks, the canon pin widened, the bracket-path lint gap fixed in-round)domain:specseat 2 (#18549) ·os-sales· sessionsession_01DhTqaEHqPVSVnAkjG3jywn· 2026-10-08T17:32Z · holder of claim6059160333. ⛔ Not a new claim.The record is
6065484352(whole-PR review at0b094026d). It finds the earlier FAIL's two reasons resolved and the formatter fix right. It fails on two more sentences of the same class in the file this diff edits. The seat re-read both at the head, and both hold. The seat's own ACCEPT6065269979missed them.packages/spec/src/shared/expression.zod.tsabout:512–:515, the exportedtmplhelper's docblock. It still says a notify node'stitle/message"are rendered by the flow interpolator, which reads single braces only", that{{record.x}}"keeps its outer braces", and thatflow-double-brace-interpolationflags it. All three claims are false at this head, and the bullet prescribes the refused form.:297–:299, theTYPED_EXPRESSION_SOURCE_REQUIREDdocblock: "The notify node'stitle/message, rendered by the flow interpolator, prescribe{record.name}". At this headNOTIFY_TEMPLATE_PLACEHOLDERis{{ record.name }}.The escalated flag, and why it is fixed here rather than filed.
templateRefsInskips a hole whose path uses bracket indexing ({{ rows[0].subjcet }}). The seat's ACCEPT left it in Acceptance notes for lack of a measured reach. The record supplies the reach, and the seat confirmed it at the head: this PR's own changeset andcontent/docs/automation/flows.mdx(about:295) teach{{ rows[0].subject }}as a text-slot spelling. In 17.xrows[0].xdid not resolve in a text slot, so this PR creates the gap, exactly as it created the formatter one that round 2 fixed in-round. The same four conditions hold: same function, mechanical, no other claim, same gate family. So it is fixed here, and no card is filed.Patch round 3 (same dev, same branch):
tmplbullet: since protocol 18 a notify node'stitle/messageread{{record.x}}too, and a single-brace token there is refused at every door.TYPED_EXPRESSION_SOURCE_REQUIREDsentence: the notify slots take their own sentences so they can name the key, and they prescribe the same{{ }}hole.packages/spec/src/shared/template-expression-input-canon.test.tswith the same predicate to the docblocks aboveexport function tmplandexport const TYPED_EXPRESSION_SOURCE_REQUIRED, so this file's canon is held as one set. Then sweeppackages/spec/src/**for any other docblock or.describe()that prescribes{var}for a notify, screen or end text slot, and fix it the same way or name it.packages/lint/src/validate-flow-template-paths.tstemplateRefsIn: inside a{{ }}hole, normalise[n]to.nbefore the dotted-path test. Pin a misspelt bracket path under a hole (finding) and a correct one (none).Not taken: the record's optional note to run the compile step even after the judge refuses (it does not change a verdict).
Next: the dev's report → the seat's check of the round's diff → a fresh contract review at
CONTRACT_REVIEW_TIER→ every check green → landing. The skills Tier H card is still owed at landing. The record notes it can be opened now; the seat files it at landing, as planned, so it cites the merged commit.Generated by Claude Code
fix(spec,lint): patch round 3 — the tmpl and typed-refusal canon, the…
fix(lint): the bracket-path pin spreads a typed record, so the test t…
test(spec): the tmpl docblock pin holds the protocol-18 canon — notif…
chore(spec): regenerate api-surface and export-origins on the merged …
objectstack-fleet commentedon Oct 8, 2026
os-dev-report
{
"issue": 22110,
"patch_round": 3,
"addendum_to": "contract review FAIL 6065484352 (at 0b09402); seat order 6065501303; seat CI note (Type Check · workspace red at 171cea0, run 37821352041, job 113462896215); seat merge note (PR dirty at b5d3cd5 after #22319 landed as 2f70c22); seat Test Core (2/6) notes (red at 5bfa9ae, job 113497475788, run 37831384372)",
"status": "done",
"branch": "claude/issue-22110-flow-text-slot-double-brace",
"pr": "#22315",
"head": "5bfa9ae1f. Round commits: 171cea0 (the four items), c673c86 (CI typecheck fix), b5d3cd5 (existing tmpl pin moved to the new canon), 0788b58 (merge of origin/main 3599fef via scripts/pm/os-regen-merge.sh), 5bfa9ae (regeneration the merge owed). The round's first merge, d2a9c95, brought in origin/main 28bff18.",
"session": "session_01DhTqaEHqPVSVnAkjG3jywn — the harness-stamped Claude-Session id of this run (subagent: the parent's)",
"premise_still_valid": true,
"summary": "The four items are in 171cea0. (1) The
tmpldocblock inpackages/spec/src/shared/expression.zod.tsno longer has a{record.x}bullet for notify. The{{record.x}}bullet now says that since protocol 18 a notify node'stitle/messageread{{record.x}}, rendered by the formula template engine, and that a single-brace token there is refused at every door (automation/flow-text-slot-template.ts). (2) TheTYPED_EXPRESSION_SOURCE_REQUIREDdocblock now says the notify slots take their own sentences so their refusals can name the key, and that since protocol 18 they prescribe the same{{ }}hole,{{ record.name }}. (3)template-expression-input-canon.test.tsuses one predicate to hold the three docblocks as one set:TemplateExpressionInputSchema,tmplandTYPED_EXPRESSION_SOURCE_REQUIRED. For each one, a notify sentence must be present, the prescription sentence must be reached, and no notify sentence may carry a single-brace placeholder unless it says refused. The sweep ofpackages/spec/src/**found no other docblock or.describe()that prescribes{var}for a notify, screen or end text slot. The two near-misses are named in out_of_scope_findings. (4) Inpackages/lint/src/validate-flow-template-paths.ts,templateRefsInnow normalises[n]to.ninside a{{ }}hole before the dotted-path test, the same normalisation formula'sresolvePathapplies. Two pins cover it:{{ caseRecord.tagz[0] }}gives one unknown-field finding namingtagz, and{{ caseRecord.tags[0] | upper }}gives none. CI cause (Type Check · workspace at 171cea0): this PR's own round-3 pin, inside the PR surface. Invalidate-flow-template-paths.test.ts,...CASE_OBJECT.fieldsspread a property ofCASE_OBJECT: AnyRec, which is anunknown, so check:test-typecheck failed on '1 type error(s) in a file the ledger does not cover'. The fix in c673c86 is...(CASE_OBJECT.fields as AnyRec). Merge (seat note): origin/main 3599fef merged through os-regen-merge.sh. The only textual conflict was the import block of service-automationengine.ts. It resolved as the union, this branch's text-slot import followed by #22319'sbuiltinNodeConfigKeysJudgedimport.registry.tsandflows.mdxmerged without conflict, and both sides are kept. The regeneration commit restored main'sbuiltinNodeConfigKeysJudgedline, which the os-regen driver had dropped, toapi-surface/automation.jsonandexport-origins/automation.json. Test Core (2/6) red at 5bfa9ae: the measuredittime at this head is the same as main's (see tests), so the red is load on the shard, not this diff.","tests": "Readings at 5bfa9ae, the merged head, after
pnpm turbo run build --concurrency=2(73/73 exit 0, under the lock): specsrc/shared src/automation src/migrations: 74 files, 2239 passed. lint whole suite: 128 files, 5853 passed. service-automation, 8 files, 108 passed: the text-slot tests (text-slot-template,notify-template-slots,notify-node,end-node-refused-outcome) plus #22319's engine tests (config-unknown-keys,decision-branch-expression-absent,nested-region-parity,predicate-slot-blank). typecheck: spec exit 0 ('check:test-typecheck: OK … 52 file(s) / 246 error(s) / 135 pinned signature(s) held'); lint exit 0 ('… 2 file(s) / 6 error(s) / 2 pinned signature(s) held', the passing reading the CI note asked for); service-automation exit 0.pnpm --filter @objectstack/spec run check:generated: '✓ All 15 generated artifacts are up to date'. Earlier readings: at c673c86 the spec suite went red inexpression-dialect-docs.pin.test.ts(2 tests, an existing pin of the oldtmplrelation; see deviations). At b5d3cd5: specsrc/shared src/automation70 files / 2024 passed; lint 128 / 5853; typecheck spec and lint OK; check:generated 15/15. Ablations, all throughnode scripts/ablation-replace.mjsWRAP mode. Each landed its mutation (anchor 1→0, blob changed on disk), ran the pin, restored, and proved blob == HEAD withgit diff HEADempty. Fix 1 at c673c86: expression.zod.ts 2fb7b4000ba2→53c8bdbd4435. The canon pin 'export function tmpl › prescribes no single-brace placeholder for a notify slot — only a sentence that refuses one names it' went red (1 failed / 5 passed). Fix 1 again at b5d3cd5, with both pins: same mutation, and both 'tmpl' pins went red (2 failed / 10 passed), the canon pin above andexpression-dialect-docs.pin.test.ts › lists the notify slots with the double-brace renderers, the single brace there as refused, and no{record.x}-only bullet. Fix 2 at c673c86: 2fb7b4000ba2→360b0d554d17. 'export const TYPED_EXPRESSION_SOURCE_REQUIRED › prescribes no single-brace placeholder…' went red (1 failed / 5 passed). Fix 4 at c673c86: the normalisation line deleted, validate-flow-template-paths.ts 8b447a926d3b→3520073d10b7. 'judges a bracket-indexed path in a{{ }}hole like its dotted form' went red (1 failed / 63 passed). CI repro at c673c86: the cast reverted, test file a7b253898cad→15c56f9bd318.pnpm --filter @objectstack/lint run typecheckexit 1 with 'src/validate-flow-template-paths.test.ts: 1 type error(s) in a file the ledger does not cover'; restored. The gate's summary line does not print the TS code, so none is quoted. Item 3 is a pin, and the fix-1 and fix-2 ablations are what redden it. The round's five source and test files are byte-identical at b5d3cd5 and 5bfa9ae (blobs 2fb7b4000ba2, 2c776882c1eb, 2bb0e89c7304, 8b447a926d3b, a7b253898cad), so these ablations hold at the merged head. Merge proof.git range-diff 28bff18d0..b5d3cd532 3599fef12..HEAD: commits 1–16 are '=', and 17 is the new regeneration commit 5bfa9ae. PR-file diff: the file set is the same 54 files before and after, and the sorted +/- line multiset ofgit diff -U0 28bff18d0 b5d3cd532equals that ofgit diff -U0 3599fef12 HEAD(2261 lines, empty interdiff). For every file both sides touched (flows.mdx, validate-expressions.test.ts, engine.ts, api-surface and export-origins automation.json, registry.ts), every line either side added is present at HEAD (missing 0 on each side). Test Core (2/6) timing,src/integration/connector-author-shape.test.ts, theitat :194. It was run at 5bfa9ae (H) and at main 3599fef (M, a detached probe worktree, removed afterwards), interleaved H M H M H M in one locked run, load average 10–24. Theit: H 19274 / 25222 / 26948 ms, M 23523 / 27609 / 24922 ms (means H 23.8 s, M 25.3 s). File wall: H 30.4 / 40.1 / 43.8 s, M 38.2 / 41.0 / 38.8 s. Deterministic counts fromtsc --extendedDiagnosticsover the test's exact three probes (two fences plus the harness self-test) with its compiler options. H: 588 files, 155870 TS lines, 280586 types, 970144 instantiations. M: 586 files, 155565 lines, 280505 types, 969975 instantiations, a +0.03% type delta. Check time: H 14.57 / 18.05 s, M 14.39 / 17.68 s. Each run reports only the harness's expected TS2739. Verdict: the head is within ~10% of main (here it is not slower). The red is the margin #22292 records, on a loaded shard; nothing in this diff introduced it. No timeout was bumped.","mcp_calls": "0",
"api_writes": "1 — POST /repos//issues/22110/comments (post-stamped.mjs via the fleet-write relay, this round-3 os-dev-report). The branch push is git, not a REST write. Reads only otherwise: the round's comments, GET pulls/22315 twice (event payloads at b5d3cd5 and 5bfa9ae), and GET actions/jobs/113462896215/logs, which failed at the log-blob redirect so nothing was read.",
"gates": {
"derived": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 5bfa9ae: 119 commands, identical to the lists at c673c86 and b5d3cd5 ('gate list derived from the tree of objectstack-ai/objectstack at commit 5bfa9ae')",
"run": "119 of 119, run at 5bfa9ae after the full build, with exit codes captured before any pipe: 119 × exit 0. The earlier round-3 battery straddled the b5d3cd5 commit and left 6 prerequisite reds from a dist that was rebuilding mid-run. It is superseded in full by this run, not patched.",
"ran_verdict": "✓ dispatch-gates --ran: 119 derived famil(ies) accounted for — 119 run, 0 NOT-MEASURED (a DERIVED zero — all 119 recorded an exit code and none of them is 3).",
"changeset": "At 5bfa9ae against merge base 3599fef.
node scripts/check-changeset-no-major.mjs --base origin/main --event(payload from GET pulls/22315 at head 5bfa9ae): exit 0, '✓ LEVEL AXIS: this PR declares clause-②yes (narrowing), and no package whosepackages/**/src/**it moves is gradedpatch', arm 'narrowing— a BREAKING change'.node scripts/check-adr-0087-registration.mjs --base origin/main: exit 0, '.changeset/22110-flow-text-slot-double-brace.md [major+BREAKING+clause-②-narrowing] registered flow-text-slot-single-brace-refused'."},
"ci": "At 5bfa9ae, per the seat: Test Core (2/6) red on the connector-author-shape :194 timeout, measured above as load, not this diff; the seat's re-run decides it. Everything else was in_progress when this report was written. This report does not wait on CI.",
"line_budget": "The round's own commits (d2a9c95..b5d3cd5): 5 files, +86 / -51. Regeneration commit 5bfa9ae: +2 generated lines (main's side restored). Merge resolution: engine.ts takes both import blocks, no other edit. Whole PR against merge base 3599fef: 54 files, +1854 / -407. Governed paths touched: 0.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted, not filed — sweep item (3):
packages/spec/src/data/context-tokens.zod.ts:109-112 and :284-287 call{recordId}a 'flow-template placeholder'. Those are value-like positions, where the single-brace spelling is still the one read, so the text is true and was left alone","carrier: 承接者:无 · noted — sweep item (3):
packages/spec/src/conversions/registry.ts:1105-1154 are the historical 17.x alias-conversion fixtures (judged right by the review), andautomation/io-node-config.zod.ts:232 and :311 are value-like positions; neither prescribes{var}for a text slot"],
"deviations": [
"One file outside the four items:
packages/spec/src/shared/expression-dialect-docs.pin.test.ts(b5d3cd5). Itstmpldescribe was an existing pin (from #22081) of the OLD relation: a{record.x}bullet naming notify and citingflow-double-brace-interpolation. Item (1) has to turn it red, and the spec suite at c673c86 failed 2 tests there. It now pins the new relation: the{{record.x}}bullet names messaging, email and notify and says refused; no bullet opens with{record.x}; and theeitherbullet namestitleFormatand not notify. Its 'relation, not wording' scope and its anti-vacuity test are kept. The fix-1 ablation at b5d3cd5 reddens it.","STEP18_RATIONALE: no conflict arose. #22319's
flow-builtin-node-config-undeclared-keys-refusedand this branch'sflow-text-slot-single-brace-refusedboth sit at order 89. The seat's 'next free order (90)' was given for a conflict, and the list's own docblock says 'Two retirements in flight may both take the same number, and then render inidorder'. Ties break by id, so builtin renders before text-slot, the same placement 90 would give. The order stays 89. If the seat wants 90 anyway, it is a one-line edit in this PR's file.","Killed my own two queued processes (pids 28897 and 28898; it was waiting in the lock queue, not holding the lock) to merge first as the merge note ordered. That build was re-run at the merged head.",
"A detached probe worktree at 3599fef (
/home/user/objectstack-main-22110-probe,pnpm install --frozen-lockfile) was created for the timing comparison. Its status was clean, and it was removed after the measurement (node_modules deleted, thengit worktree removewithout --force).","PR body not edited, per the order. The seat rewrites the Acceptance-notes bracket line. A suggested
## Patch round 3section: review FAIL 6065484352, order 6065501303 → 171cea0 (four items), c673c86 (lint test typecheck: typed spread in the bracket pin), b5d3cd5 (existing tmpl pin moved to the protocol-18 relation), 0788b58 / 5bfa9ae (merge of 3599fef and its regeneration). Add the ablation and gate readings above.","The first round-3 command chain stopped at a
git rev-parseerror (it was given two revisions) in the shared checkout. Only read-only git had run, and the shared checkout was verified untouched before redoing the steps in the worktree. Several lock calls hit the queue timeout (exit 99) and were retried under the same slot."],
"files_changed": [
"packages/spec/src/shared/expression.zod.ts (tmpl and TYPED_EXPRESSION_SOURCE_REQUIRED docblocks)",
"packages/spec/src/shared/template-expression-input-canon.test.ts (canon pin widened to three docblocks)",
"packages/spec/src/shared/expression-dialect-docs.pin.test.ts (existing tmpl pin moved to the new relation)",
"packages/lint/src/validate-flow-template-paths.ts (templateRefsIn bracket normalisation + docblock)",
"packages/lint/src/validate-flow-template-paths.test.ts (bracket-path pin, typed spread)",
"packages/services/service-automation/src/engine.ts (merge conflict only: both import blocks kept)",
"packages/spec/api-surface/automation.json, packages/spec/export-origins/automation.json (regenerated: main's builtinNodeConfigKeysJudged restored)"
],
"cleanup": "the worktree ../objectstack-issue-22110 is removed after this comment; the probe worktree is already removed"
}
objectstack-fleet commentedon Oct 8, 2026
Seat check of patch round 3 on PR #22315 at
5bfa9ae1f: ACCEPT. Next: a fresh contract review on this headdomain:specseat 2 (#18549) ·os-sales· sessionsession_01DhTqaEHqPVSVnAkjG3jywn· 2026-10-08T20:29Z · holder of claim6059160333. ⛔ Not a new claim.The report is
6068392390. The seat read the round's own commits, not the merge:171cea002,c673c8635,b5d3cd532and the regeneration5bfa9ae1f.packages/spec/src/shared/expression.zod.ts:tmplbullet now says that since protocol 18 a notify node'stitle/messageread{{record.x}}and that a single brace is refused at every door.TYPED_EXPRESSION_SOURCE_REQUIREDsentence now says the notify slots take their own sentences so they can name the key, and that they prescribe the same{{ }}hole.6065484352(①.17, ①.18) are true at this head.b5d3cd532moves the olderexpression-dialect-docs.pin.test.tsrelation, which item (1) had to turn red, to the protocol-18 one. The sweep's near-misses (context-tokens.zod.ts{recordId}, the 17.x fixtures inconversions/registry.ts,io-node-config.zod.ts's value-like positions) all describe value-like positions or history, so their text is still true.templateRefsInreads[i]as.ibefore the dotted-path test. That is the same normalisation as the formula engine'sresolvePath. A quoted index stays unjudged, as an arithmetic token. It has one finding pin and one no-finding pin.c673c8635is the typed spread that fixed theType Check · workspacered.0788b58e1,origin/main3599fef12, viaos-regen-merge.sh):engine.ts's import block, resolved as the union.5bfa9ae1frestores main'sbuiltinNodeConfigKeysJudgedinapi-surface/export-origins, which the driver had dropped.=for commits 1–16. The line multiset is unchanged across the merge.STEP18_RATIONALEentry stays at order 89, tied with fix(spec,service-automation)!: an undeclared config key on 10 more builtin node types is refused at the build doors; one judge per type #22319's. The docblock allows ties, so it is not changed.6067723298):Test Core (6/6)is finding(ci): the shard-timings dataset rests on ONE scheduled run, and records @objectstack/spec at 1134.86 s against 1573–1651 s executed — #16468's 25%-headroom ceilings built on it would red every PR that runs spec #22014's timing drift.Test Core (2/6)isconnector-author-shape.test.ts:194at its 60 s budget, the margin hourly full run: red on main (CI) #22292 records on main. Measured interleaved, head 19.3 / 25.2 / 26.9 s vs main 23.5 / 27.6 / 24.9 s, with +0.03% tsc instantiations, so this PR does not make it slower.mainin after the review if no round follows.## Patch round 3section carries the readings.Owed at landing, unchanged: a Tier H card for
skills/objectstack-automation/SKILL.md(:108–:109,:224–:236) andevals/flows-triggers-approvals.json:17.Next: a fresh
## Contract reviewatCONTRACT_REVIEW_TIERon5bfa9ae1f, from an isolated subagent → every check green → the landing to-do in6063281763.Generated by Claude Code
fix(spec): a notify title / message refusal prescribes the single-bra…
feat(spec)!: flow value slots refuse the `{…}` template dialect, nami…
objectstack-fleet commentedon Oct 9, 2026
os-dev-report
{ "issue": 22110, "status": "done", "branch": "claude/issue-22110-flow-text-slot-double-brace", "pr": "https://github.com/objectstack-ai/objectstack/pull/22315", "session": "session_01DhTqaEHqPVSVnAkjG3jywn (the dispatching seat's session; this executor ran as its subagent)", "premise_still_valid": true, "summary": "Merge-main round only: no behaviour change and no edit beyond the merges themselves. FINAL HEAD 4e275f15d (4e275f15df0e5c991442b0b49ab4b02800678f4a), pushed, equal to the remote branch. Two merges of origin/main, both through scripts/pm/os-regen-merge.sh (never a rebase, never a force-push): (1) f84cd82d2 = bff973d87 + main ca135dcc4 (39 incoming commits incl. #22380, #22392, #22393, #22406, #22368); (2) on the seat's note, 4e275f15d = f84cd82d2 + main 3054516ef (4 incoming commits incl. 27a8b33de, the #22416 fix; git merge-base --is-ancestor 27a8b33de HEAD exits 0). CONFLICTED FILES: none in either merge (zero conflict markers, the script's step 1 completed with exit 0 and step 3 had nothing left to commit). Files edited on BOTH sides in merge 1, each auto-merged by git with disjoint hunks and read after the merge: packages/spec/src/migrations/registry.ts (the branch's step-18 fragment and D3 entry, main's two new fragments, two new D3 entries and a rewording of the flow-builtin-node-config try_catch text); packages/lint/src/validate-expressions.ts (branch: the flow text-slot pass, flowNodeTextSlotSources and textSlotTemplateRefusal; main #22392: the select-option visibleWhen root and member verdicts, OPTION_VISIBLE_WHEN_BOUND_ROOTS and OPTION_VISIBLE_WHEN_BOUND_MEMBERS; both kept); packages/lint/src/validate-expressions.test.ts; packages/services/service-automation/src/engine.ts (main changed comments and the runAs warn text only); packages/services/service-automation/README.md; examples/app-showcase/src/automation/flows/index.ts (main changed a comment on InboundTaskWebhookFlow only); content/docs/automation/flows.mdx. Merge 2 overlapped no file the branch edits. No same-logic semantic choice arose, so nothing was escalated. STEP18_RATIONALE is the union of both sides in id order: 107 fragments, 0 out of order. flow-text-slot-single-brace-refused (branch) sits between flow-script-subflow-config-undeclared-keys-refused and flow-value-slot-template-dialect-refused. sys-view-definition-retired and try-catch-and-retry-policy-undeclared-keys-refused (main) sit at their sorted positions. GENERATED ARTIFACTS: os-regen step 2 took main's side of nothing, because no os-regen path changed on both sides. It kept the branch's bytes of the four paths only the branch changed (references builtin-node-config.mdx and io-node-config.mdx, api-surface/automation.json, export-origins/automation.json). gen:migration-registry, gen:spec-changes and gen:upgrade-guide each regenerated byte-identical: git status was empty afterwards, so no regeneration commit was needed. check:generated reports 15/15 current. No os-regen-pending marker was left. Worktree objectstack-issue-22110-m removed after the push (node_modules deleted, plain git worktree remove).", "tests": "All at FINAL HEAD 4e275f15d after a full workspace build (pnpm exec turbo run build --concurrency=2 --filter=!@objectstack/docs: 72/72 tasks, lock VERDICT command-exit 0). Unit tiers, in full, under scripts/pm/os-verify-lock.sh: @objectstack/spec 629 files, 18779 passed and 1 todo; @objectstack/formula 44 files, 1269 passed; @objectstack/lint 129 files, 5891 passed; @objectstack/service-automation 179 files, 2193 passed (lock VERDICT command-exit 0 on each). The spec tier includes packages/spec/scripts/step18-rationale-merge.test.ts, the registry's id-order and merge pin. Typecheck of the same four packages: VERDICT command-exit 0 (tsc --noEmit plus check:test-typecheck, and check:scripts-typecheck for spec). Gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 120 commands at 4e275f15d over 54 paths (+1854/-407). That list is byte-identical to the one derived at f84cd82d2. All 120 were run with exit codes captured before any pipe, and all 120 exited 0. Then --ran answered: 120 derived, 120 run, 0 NOT-MEASURED (a derived zero, every line carrying its exit code), 0 UNRUN. check:pm-dispatch-gates at 4e275f15d: 2011 cases pass, exit 0. At f84cd82d2 it failed 1 of 2011 at packages/qa/dogfood/test/security-catalog-cold-boot-environment-holder.dogfood.test.ts:108 (not this diff, #22422; red on main too; fixed by 27a8b33de, which the second merge brought in). check:nul-bytes OK (10409 files). Process notes: at f84cd82d2, check:skill-examples and check:dual-build-cjs-loads first refused with exit 3, PREREQUISITE NOT MET, because client-react, studio and six plugin and service dists were missing. Both exited 0 after the full build. At 4e275f15d the build ran first, and every gate passed on its first run. Three first test attempts at 4e275f15d ended in lock queue-timeout exit 99, which means NOT MEASURED. They were rerun under the same OS_VERIFY_LOCK_SLOT, and the counts above come from those reruns. Not run locally, declared to CI: Test Core shards and their timing-drift step, Dogfood Regression Gate (the branch's packages/qa/dogfood edits), Dogfood Verify CLI, Temporal Conformance, Build Core and Build Docs, the workspace type-check lanes, and the examples' own test suites (app-todo, app-showcase).", "mcp_calls": "0", "api_writes": "1: POST /repos/objectstack-ai/objectstack/issues/22110/comments, this report, through scripts/pm/post-stamped.mjs. The two git pushes (bff973d87..f84cd82d2 and f84cd82d2..4e275f15d) are not REST writes. Reads only besides that: one GET of the #22110 comments, to verify the newest Claim names this branch (it does). The PR body was not touched.", "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
objectstack-fleet commentedon Oct 9, 2026
os-dev-report
{ "issue": 22110, "status": "done", "branch": "claude/issue-22110-flow-text-slot-double-brace", "pr": "https://github.com/objectstack-ai/objectstack/pull/22315", "session": "session_01DhTqaEHqPVSVnAkjG3jywn (the dispatching seat's session; this executor ran as its subagent)", "premise_still_valid": true, "summary": "Merge-main round 3. NEW HEAD ce4f6519f (ce4f6519fc9aad5d8665bb6da8615b8f75bed191), pushed 4e275f15d..ce4f6519f, equal to the remote branch. It is one merge of origin/main 05c7c3fa3 (7 incoming commits, incl. 46692c118 = #22427), made in a fresh worktree objectstack-issue-22110-m2 on the remote head 4e275f15d through scripts/pm/os-regen-merge.sh (never a rebase, never a force-push). CONFLICTED FILE, the only one: packages/lint/src/validate-expressions.test.ts, one hunk. It is not inside any test case. It is the PLUMBING receiver-excuse set of the receiver-scan test (its assertion: every scanned receiver is either tabled or in PLUMBING). Both sides appended to the end of that set at the same spot. OURS (branch, #22110): a 4-line comment plus 'slot', the spec text-slot locator's local, whose keys are path, label and source. THEIRS (main, #22394 via #22427): a 4-line comment plus 'declaredUserMembers', 'boundUserMembers', 'listedNames', 'tickedNames', 'membersRead', the EvalUser member-list locals. RESOLUTION: the verbatim union, the branch's block then main's, with no character of either side edited. The set's members are order-free, and no assertion changed. Neither side's setup had to change to fit the other: each entry excuses only its own side's local, and dropping either would turn the scan red for that side. Proof of the union: 0 lines either side added (vs merge base 3054516ef) are missing from the merged file, and 0 test titles of either parent are missing. The merged file has 297 titles, the same as main's 297, which contain the branch's 290: the branch adds no case to this file. CASES KEPT FROM MAIN (#22427), every one, in the describe 'a per-option visibleWhen member of the acting user the option check does not bind is refused (#22394)': (1) it.each 'refuses %s at error, located at the option, naming the member and the members that are bound'; (2) 'the roles refusal names positions in the spelling the author wrote, and that replacement passes and evaluates'; (3) 'CONTROL: every bound member under every spelling, a grant check, and a record field named like a refused member pass'; (4) 'POSITIVE CONTROL: the same current_user.email read in an object action predicate, a surface the refusal does not judge, is not refused'; (5) 'judges every member spelling as one read: optional, indexed and has()'; (6) 'one finding per option: an unbound root, then a namespace member, then members in SCOPE_ROOTS and name order'; (7) 'every receiver the option check binds judges its members against a declared source'. Also kept: main's five PLUMBING entries above. KEPT FROM THE BRANCH: its 'slot' PLUMBING entry, the branch's only edit to this file. The branch's text-slot cases live in packages/lint/src/validate-expressions.text-slot.test.ts, which main did not touch (0 lines changed), so all 5 are kept unchanged: (1) refuses a single-brace token in a notify title / message and a screen title / description, at error, with the hole spelling; (2) compiles a slot with no single-brace token: logic, an unknown formatter or an unbalanced hole is an error; (3) reports a hole touching exactly one brace as the compile step's unbalanced hole, with no three-brace rewrite; (4) passes holes, formatters and a dollar-named variable, and every single-brace slot that is not text; (5) judges an end message too, for a stack handed to validateStackExpressions with no parse in front of it. AUTO-MERGED, both sides edited, disjoint hunks, read after the merge: packages/lint/src/validate-expressions.ts (main #22427 extends the option visibleWhen verdicts, around 1115 to 1417 and 2261; the branch's text-slot pass and imports are intact); content/docs/automation/flows.mdx (main adds a 403 row and rewords the trigger route's outcome table, outside every section the branch edits); examples/app-showcase/src/automation/flows/index.ts (main re-types InquiryPurgeFlow from autolaunched to screen, a flow type with no text-slot node). GENERATED: on the script's rerun, step 2 took main's side of nothing and kept the branch's bytes of the same four paths as before, and step 3 had nothing to commit. No regeneration commit was needed (see tests). The merge commit carries the model-free trailer pair. Worktree removed after the report.", "tests": "At NEW HEAD ce4f6519f, after a full workspace build (turbo build --concurrency=2 --filter=!@objectstack/docs: 72/72 tasks, lock VERDICT command-exit 0). Lint unit tier in full, under scripts/pm/os-verify-lock.sh (pnpm --filter @objectstack/lint test): 129 test files passed, 5904 tests passed, lock VERDICT command-exit 0. That is 13 more than the 5891 at 4e275f15d: #22427's cases, with its it.each expanded. Lint typecheck (tsc --noEmit plus check:test-typecheck): VERDICT command-exit 0. Gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 120 commands at ce4f6519f over 54 paths (+1854/-407) vs merge base 05c7c3fa3. Both the command list and the delta are identical to round 2's, so the PR's delta against main is unchanged by this merge. All 120 were run with exit codes captured before any pipe, and all 120 exited 0, including check:pm-dispatch-gates (2011 cases pass) and spec check:generated. Then --ran answered: 120 derived, 120 run, 0 NOT-MEASURED (a derived zero), 0 UNRUN. check:nul-bytes OK. NOT MEASURED, extra beyond the asked scope: a verbose targeted run of the two test files above hit lock queue-timeout exit 99 twice. Both files are inside the green full-tier run. Spec, formula and service-automation tiers were not re-run this round. #22427 touched none of them, and main's spec edit (src/data/hook.zod.ts) is outside the branch's files. They are covered by the derived gates and CI. Declared to CI as before: Test Core shards and their timing-drift step, Dogfood Regression Gate, Dogfood Verify CLI, Temporal Conformance, Build Core and Build Docs, the workspace type-check lanes.", "mcp_calls": "0", "api_writes": "1: POST /repos/objectstack-ai/objectstack/issues/22110/comments, this report, through scripts/pm/post-stamped.mjs. The git push 4e275f15d..ce4f6519f is not a REST write. The PR body was not touched.", "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
objectstack-fleet commentedon Oct 9, 2026
Landed: PR #22315 →
2b61f2d9d(Fixes #22110). The card is closedcompleted; the Tier H follow-up is filed as #22454domain:specseat 2 (#18549) ·os-sales· sessionsession_01DhTqaEHqPVSVnAkjG3jywn· 2026-10-09T09:43Z · holder of claim6059160333.2b61f2d9d. It has one parent,4f4c4ed81, and is an ancestor oforigin/main. There was no queue ejection.Fixesclosed this card.ce4f6519f(contract review PASS6077952132, after the merge rounds6076389514/6077810372):2b61f2d9dare blob-equal to that head.main, and the queue merged both sides. Every line this PR adds is present on2b61f2d9d:lint/src/validate-expressions.ts: 21 of 21;validate-expressions.test.ts: 5 of 5;migrations/registry.ts: 53 of 53;dogfood/test/expression-conformance.ledger.ts: 3 of 3.@objectstack/spec/service-automation/lintmajoron the v18 line,@objectstack/formulaminor;Clause-②: yes (narrowing); ADR-0087registered flow-text-slot-single-brace-refused):{{ }}delimiter: notifytitle/message, screentitle/description, endmessage.{token}there is refused at the build doors and the save door, with the hole spelling as its remedy. One judge serves both doors.{token}in notify text slots, which PR #22315 now refuses (Tier H, owed at #22110) #22454 (Tier H,domain:skillsonce graded).skills/objectstack-automation/SKILL.md:108–:109and:236–:241, and eval id 2 inevals/flows-triggers-approvals.json, still teach single-brace text slots.{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 pass 2 (service-automationbuiltin/template.ts) and finding(spec/automation): the value-slot refusal remedy spellslist[0]for a variable namedlist— a CEL type name, so the remedy it tells the author to copy does not evaluate #22290 were held behind this card in the lane's serial queue. Both stay inpm:queue.content/docs/automation/flows.mdx's fault-edge variable table (about:1624) still shows a single-braced example with no position named;stringifyForTemplatedocblock (template.tsabout:332–:335).Both are residue that the reviews recorded as not being conditions.
This act removes
pm:dispatchedfrom the closed card; the domain, area, priority, target and type labels stay.Generated by Claude Code