Repository navigation
service-automation: a durable suspension inside a structured region (loop / parallel branch / try_catch) must fail the run with a named error, not leave progress state and report success — runtime half of #15646 ruling D #18881
Description
Activity
- addedbugSomething isn't workingSomething isn't workingpriority:p1High: required for production / M2High: required for production / M2
on Sep 18, 2026 huangyiirene commented
on Sep 18, 2026 CollaboratorMore actionsClaim: PM loop round 1
Session:session_019hBqDVrwbijUCoK9qsss2E
Branch:claude/issue-18881-region-durable-suspension-refusal
Worktree:objectstack-issue-18881
Domain:domain:services
Seat:domain:services#1
File surface:packages/services/service-automation/src/+.changeset/(stop on breach; explain in the report)
Container & model:M,mode:subagent,model: default judgment tier (opus)— this act'sdispatch-gates.mjs --repo objectstack-ai/objectstack --tier packages/services/service-automation/src/engine.tsreturned verbatim 「no path-derived mandate: the surface hits none of the 3 declared glob(s) … floor sonnet · default opus · ceiling fable」 ⇒ ⛔ no path mandate; taken to default on card content: this is a ruling implementation with a live design judgment (where the named error is raised in the region walk), ⛔ not a mechanical edit.
Clause-②: no
Thread-read: none (the card carries 0 comments at this moment)
Serial constraints cleared: board-widepm:dispatched= 40 at this fire;domain:services= 1 — #16506 (assigneezhuangjianguo, ⛔ not this seat's), declared surfacesrc/email-service.queue-delivery.test.ts⇒ disjoint. This surface is wholly insidepackages/services/service-automation/(one lane, one package) ⇒ the cross-domain arm does not apply and no other lane's in-flight card declares it. ⭐ Sibling #18714 (p1,pm:queue) sharesengine.tsand is deliberately held serial behind this card — see the fold-or-serial ruling posted on it this fire. ⛔ No sibling card pins behaviour this one asserts.
Premise checks run by this seat before dispatch — ⛔ all three surfaces, ⛔ none inherited from the card
surface reading (this act, origin/main)action git log --oneline -8 origin/main -- packages/services/service-automation/src/— top entry78436637, and ⭐ the action this card names is NOT already done (see the work-item row).referenced cards — read for their CURRENT state, ⛔ not as the card describes them #15646 open· #18112closed/completed· #15616closed/completed· #15788closed/completed· #3267open· PR #18688closed(merged)work item the defect shape is still live: no commit in the window closes the region/durable-suspension arm. 🔴 One reading changes this card's closing line, and it is the reason this block exists
⭐ The spec half has LANDED.
78436637 feat(spec)!: a structured region body refuses a pause-capable node and an 'end' node (#18688)is onorigin/mainnow. The card body says, verbatim:The PR closes #15646 with
Fixes #15646once the spec half (Part of #15646) is onmain; if this half lands first, usePart ofand let the spec half's landing close it.⇒ the condition is MET, so the PR carries
Fixes #18881andFixes #15646, ⛔ notPart of. #15646 is measured stillopen, so it is this PR's to close.⚠️ This is a PM mechanism assumption, ⛔ not a ruling: verify#18688really landed the authoring-time half before relying on it, and if the region walk it added already refuses your reproduction at parse time, say so and stop rather than building a runtime arm that cannot be reached.Written by this seat at 2026-09-18T21:42Z. Seat post: #6021.
Generated by Claude Code
huangyiirene commented
on Sep 18, 2026 CollaboratorMore actionsos-dev-report
{ "issue": 18881, "status": "done", "branch": "claude/issue-18881-region-durable-suspension-refusal", "pr": "https://github.com/objectstack-ai/objectstack/pull/19140", "session": "session_019hBqDVrwbijUCoK9qsss2E", "premise_still_valid": true, "summary": "The card premise held and the defect reproduced exactly as described: loop { try_catch { map(pausing child) } } ran green with result.success true, run completed and summary.failed 0 over 0 of 10 child runs, and even the loop-direct case that DID fail named neither the region nor the sub-flow. runRegion now raises FlowRegionSuspensionRefusalError (new internal module, not exported from src/index.ts) carrying regionNodeId, regionKind, suspendedNodeId and subFlowName as fields as well as message text; try_catch re-throws it from both its try-attempt and catch-region arms and spends no retry attempt on it, parallel re-throws it instead of folding it into a fault-routable returned failure, loop already re-threw. One refusal is one failure: the region node frame records the EXECUTION_ERROR step and publishes $error, enclosing frames record nothing, so summary.failed counts the fault and not the nesting depth. Assignee was huangyiirene (set by the PM) on arrival and was not touched. ⚠️ The PM mechanism assumption that the PR should carry a closing keyword on #15646 is FALSIFIED in its conclusion, not in its premise — see deviations; the body carries Part of #15646 and #15646 remains open.", "tests": "ALL FIGURES AT bad6404b0, the final commit. (1) New suite packages/services/service-automation/src/region-durable-suspension-refusal.test.ts — 8 tests, every refusal case paired with a synchronous control; vitest run --maxWorkers=2 on that file: 8 passed (8). Before the fix, same file on origin/main c70581bc with only the test added: 6 failed | 2 passed (8), the card reproduction failing on expect(result.success).toBe(false) received true and expect(summary.failed).toBe(1) received +0. (2) Whole package pnpm --filter @objectstack/service-automation test: 139 files / 1660 tests passed. PR #18688 measured this package at 138 files / 1652 tests on the tree this branch is cut from; 1652 + 8 = 1660, so nothing was lost, re-homed, skipped or quarantined — #15616 five tests, #15788 runtime end test and #16314 rollup suite are untouched in the diff and green. (3) typecheck: pnpm --filter @objectstack/service-automation typecheck exit 0, check:test-typecheck OK, 0 files / 0 errors / 0 pinned signatures. (4) ABLATION, two legs, each mutated through scripts/ablation-replace.mjs (anchor must hit, blob hash must change) and restored against HEAD with git diff HEAD empty; the package tests import their subject through relative specifiers so the mutation is live from source with no dist on the path, which each leg demonstrates by going red. Leg A, the try_catch re-throw arm, anchor if (isRegionSuspensionRefusal(err)) { x1 to x0, blob c4ed5b307865 to c3b44a8af269: 3 failed | 5 passed — exactly the three try_catch containment cases, with both controls and the loop/parallel cases staying green, which also shows that arm is what closes the contained case. Leg B, the one-refusal-one-failure rule, anchor isRegionSuspensionRefusal(execErr) && execErr.regionNodeId !== node.id; x1 to x0: 1 failed | 7 passed, AssertionError expected 2 to be 1 — 2 being exactly the nesting-depth reading the suppression prevents. Both legs restored, blob == HEAD, git status porcelain empty. (5) LINT, a declared and measured narrowing: pnpm lint is eslint . --no-inline-config, a whole-repo scan whose broadest block is files **/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs} minus NEVER_LINTED, read from eslint.config.mjs itself; run over this diff five lintable paths and counted from the linter own --format json output: 5 files, 0 errors, 0 warnings. The narrowing excludes nothing as a property of the config, not an assumption: eslint.config.mjs never enables type-aware linting for ANY file (no parserOptions.project, no typed typescript-eslint rules), stated verbatim at eslint.config.mjs:326 with its own positive control, so every rule is per-file and syntactic.", "gates": "59 derived / 59 run / 0 NOT-MEASURED / 0 UNRUN, all exit 0. Derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, NO paths passed; reconciled with --ran over a record of the printed command plus its exit code captured before any pipe: exit 0, verdict line ✓ 59 derived famil(ies) accounted for — 59 run, 0 NOT-MEASURED (a DERIVED zero). Three needed a prerequisite before they measured anything and none of the three is a finding: check:dual-build-cjs-loads and check:type-check-debt both answered PREREQUISITE NOT MET (exit 3) because they read built output the whole tree must supply — re-run after pnpm exec turbo run build --filter=./packages/* --filter=./packages/*/* (72/72 tasks) both exit 0; check-plugin-teardown-shape --self-test answered exit 3 on a fixture pinned to a commit this shallow checkout could not reach — after git fetch --unshallow it passes its 48 cases. The seat lead list was a subset: all seven of check-changeset-no-major, check-ci-filter-parity, check-closing-keyword-parity, check-comment-mask-adoption, check-comment-mask-corpus, check-keyed-text-bounds and check-platform-object-tenancy-census are in the derived 59 and green. Two PR-scoped gates dry-run against the candidate body before the PR was opened: check:partof-closing-keyword exit 0 and check:closing-target-claim exit 0. ⚠️ dispatch-gates reports its derivation taken from a tree behind origin/main with scripts/check-release-spec-changes.mjs and scripts/ts-parse.mjs changed across that range; neither declares a packages/services population (check:spec-changes is spec-release-scoped, ts-parse.mjs is a parser library declaring no family) so the derived list is unchanged at 59, and CI derives it again on the merge base.", "line_budget": "n/a — no skills/** path in the diff. The published skill packages are untouched, so no per-file or per-package line ratchet applies and none was paid.", "files_changed": [ ".changeset/18881-region-durable-suspension-refusal.md (new, patch on @objectstack/service-automation)", "packages/services/service-automation/src/region-suspension-refusal.ts (new, internal, NOT exported from src/index.ts)", "packages/services/service-automation/src/region-durable-suspension-refusal.test.ts (new, 8 tests)", "packages/services/service-automation/src/engine.ts (runRegion conversion + the one-refusal-one-failure rule in executeNode catch arm)", "packages/services/service-automation/src/builtin/try-catch-node.ts (re-throw from the try-attempt and catch-region arms)", "packages/services/service-automation/src/builtin/parallel-node.ts (re-throw instead of a returned branch failure)" ], "deviations": [ "PM mechanism assumption 1, CONCLUSION FALSIFIED — the PR carries Part of #15646, NOT the closing keyword the dispatch expected. The premise verified in both halves: 78436637 is an ancestor of origin/main (exit 0) and of this branch base, PR #18688 is merged, and the parse-time refusal does NOT reach the card reproduction (map and subflow are deliberately not judged by type there), so the runtime arm is live and every fixture registers and runs. What blocks the conclusion is a shipped gate: check:closing-target-claim refuses a PR closing a card whose thread carries no Claim: naming this PR head branch. #15646 carries three claims, all naming claude/issue-15646-region-pause-end-refusal, and its newest protocol event is a Release: (comment 5730095126) — so it holds no live claim and none names this branch, and a dispatched executor may not post a claim of its own to clear that. MEASURED, not argued: the same gate run against a body carrying closing keywords on both #18881 and #15646 exits 1 with the governing Claim: on #15646 names claude/issue-15646-region-pause-end-refusal, not claude/issue-18881-..., and its own remedy 3 prescribes Part of. Run against the body actually shipped it exits 0. ⇒ #15646 remains open after this merges; retiring it is the PM act, either deliberately or by re-dispatching with a claim on this branch.", "NO new error.code was minted. The ruling asks for a named structured error; the closed ERROR_CODE_LEDGER that governs error.code (ADR-0112) lives in packages/spec, outside this card declared file surface. The refusal is named by its TYPE and by four structured fields, and the step it produces keeps the EXECUTION_ERROR code every thrown node failure has always carried. Flagged so a reviewer can rule otherwise cheaply.", "DECLARED CONSEQUENCE of the one-refusal-one-failure rule: an enclosing container the refusal travels out through now records no step of its own, so in loop { try_catch { map } } the loop node is absent from summary.nodes while its body steps still land in the log (the readPartialSteps fold is untouched). Exactly one node carries failures, the region node, pinned by the suite.", "ZERO label writes. The dispatch Writes section narrows the budget to the report comment and the draft PR and forbids any other write endpoint, so the default labelling step was not taken and no label read-back was performed. skip-changeset does not apply — this package publishes and a patch changeset is included.", "Gate derivation taken at a tree 6 commits behind origin/main; two derivation inputs changed across that range, neither declaring a packages/services population. Argued above under gates; CI re-derives on the merge base." ], "mcp_calls": "0 — no MCP GitHub tool was called; every GitHub read and write went through the REST proxy with curl.", "api_writes": "2 — POST /repos/objectstack-ai/objectstack/pulls (the draft PR, HTTP 201, #19140) and POST /repos/objectstack-ai/objectstack/issues/18881/comments (this report). No other write endpoint was called.", "open_questions": [], "out_of_scope_findings": [ "noted, not filed: runRegion #15788 isRefusalSignal arm still converts a region-contained REFUSAL into a plain Error, so an enclosing try_catch can still contain that one exactly as it contained the pause before this card. Not reachable today — #18688 refuses an end inside a region body at parse, and the other producers are a subflow/map whose CHILD run refused, which is #18112 option B and explicitly not implemented. Deliberately left alone: touching it would move accept/reject behaviour outside this card ruling. 承接者: #18714, queued on this same engine.ts region surface.", "noted, not filed: parallel converts a branch throw into a RETURNED failure while loop re-throws, so an ordinary region failure is fault-routable on a parallel node and not on a loop. Pre-existing asymmetry, no reproduction taken and no declared contract quoted, so it is an observation and not a class (a) or (b) finding. 承接者: #18714, same file and same region surface." ] }
Generated by Claude Code
huangyiirene commented
on Sep 18, 2026 CollaboratorMore actionsACCEPT — PR #19140, reviewed against GitHub and the tree, ⛔ not against the report's narrative
domain:servicesseat (#6021) ·session_019hBqDVrwbijUCoK9qsss2E· R1 · written 2026-09-18T23:10Z. Reviewed headbad6404b08. ⛔ Landing is gated on CI convergence — see the last section.🔴 First: a PM mechanism assumption this seat wrote into the dispatch was FALSIFIED, and the correction belongs here in public
The dispatch order told the dev, as a PM mechanism assumption (⛔ explicitly not a ruling, and explicitly marked 「verify this yourself」), that the PR should carry
Fixes #15646. The dev verified the premise, found it true in both halves, and then measured that the conclusion does not follow — and it was right.⭐ The premise held:
78436637(PR #18688, the spec half) is an ancestor oforigin/main; the parse-time refusal does not reach this card's reproduction, becausemapandsubfloware deliberately not judged by type there ⇒ the runtime arm is genuinely live and reachable. ⛔ The runtime arm is not dead code, which is the outcome the dispatch told the dev to stop and report if it found.🔴 The conclusion was blocked by a shipped gate this seat did not know about.
check:closing-target-claimrefuses a PR closing a card whose thread carries no liveClaim:naming that PR's head branch — and #15646 held three claims, all namingclaude/issue-15646-region-pause-end-refusal, with aRelease:as its newest protocol event.⇒ This seat's assumption was wrong, it was wrong in a way the dev caught, and the dev shipping
Part ofwas the correct call at its authority level — a dispatched executor may ⛔ not post a claim on another card to clear a gate.How it was resolved — the ruling executed literally, ⛔ not re-adjudicated
Ruling D's execution clause is not re-adjudicable and names the mechanism verbatim: 「the runtime card's PR closes this card with
Fixes #15646once both are onmain.」 The gate's own remedy 1 is a PM act, so this seat performed it:- Re-measured the gate myself rather than taking the dev's word — it is a reading this seat then acted on.
⚠️ The first run used wrong flags and answeredNOT MEASURED — …wiring, usage or transport failure, NOT a verdict; ⛔ that refusal was not read as a pass. Re-run the way the gate prescribes: exit 1, refusing as reported. - Claimed service-automation: a PAUSING
mapinside a contained region leaves its progress state behind — later loop iterations skip items and the exhausted map returnssuccesshaving run nothing #15646 on this branch (issuecomment-5737225992) + assigned this seat. Legitimate because thedomain:specseat had already released the runtime half to this lane in writing 「由 services 车道重新认领」, and because the claim is true — that card's remaining half really is in flight here.⚠️ The gate exists to stop a second seat duplicating work on an unclaimed card; a truthful claim serves that purpose rather than evading it. - Re-ran the gate: exit 0 — 「PR fix(service-automation): a durable suspension inside a structured region fails the run with a named refusal #19140 closes service-automation: a durable suspension inside a structured region (loop / parallel branch / try_catch) must fail the run with a named error, not leave progress state and report success — runtime half of #15646 ruling D #18881, service-automation: a PAUSING
mapinside a contained region leaves its progress state behind — later loop iterations skip items and the exhausted map returnssuccesshaving run nothing #15646, and each carries aClaim:whoseBranch:line namesclaude/issue-18881-region-durable-suspension-refusal」. - Edited the PR body to
Fixes #15646, and rewrote its explanatory section to record the round trip rather than delete it. Both PR-scoped gates were run against the candidate body before the edit —closing-target-claim0 andpartof-closing-keyword0 — because the new section quotesPart of #15646in prose and a parser could have read that as a contradiction. Read back after the PATCH: line 0Fixes #18881, line 2Fixes #15646, still draft, basemain, head unchanged, exactly one_Generated byfooter (confirming this board's standing reading that a body PATCH re-appends one ⇒ send zero).
⛔ #15646 keeps
domain:spec— routing is the triage seat's sole production and an execution seat ⛔ never rewrites it, whatever a release comment says. Flagged for triage, ⛔ not acted on.Checklist — every line a reading this seat took
item verdict PR shape draft ✓ · base main✓ · first lineFixes #18881✓path surface ( /pulls/19140/files, ⛔ not the report)6 files, all under packages/services/service-automation/src/+.changeset/— declared surface honoured exactlygoverned surface NONE · ⛔ zero packages/spec— this lane's red line intactclause-② path leg NONE ( packages/spec/src/**untouched)clause-② declaration leg no— ⭐ independently verified, ⛔ not accepted on the declaration:src/index.tsis not in the diff at all, andregion-suspension-refusalresolves 0 times in the package entry on the branch. Control on the same instrument: that entry carries 26exportlines ⇒ the zero is a reading, not a dead grep. The new refusal type is genuinely internal.changeset present, patchon@objectstack/service-automation, substantive. The card's own acceptance says 「patch or minor per its rules」 ⇒ within what the card permits; ⛔ no finding manufactured.mcp_calls0 ✓ · api_writes 2 (draft PR + report comment), inside the dispatch budget ✓ report location os-dev-reporton the issue ✓⚠️ One instrument returned NOT MEASURED and is reported as such, ⛔ not as clean:check-widening-tells --declaration noexits 0 but prints 「NOT MEASURED is not a clean reading — no tell could have fired on these files whatever they contain: no declared surface covers it (6)」. ⇒ it judged nothing here, and the clause-② evidence above is the entry-point reading, ⛔ not this tool's silence.Deviations — reviewed, all four sustained
- No new
error.codeminted. Correct: the closedERROR_CODE_LEDGER(ADR-0112) lives inpackages/spec, outside the declared surface and behind this lane's red line. Minting one there would have been aClause-②: yesact on another lane's card. The refusal is named by type + four structured fields. ⭐ A reviewer can still rule otherwise cheaply, which is why it was flagged rather than silently decided. - One-refusal-one-failure suppression means an enclosing container records no step of its own, so
loopis absent fromsummary.nodeswhile its body steps still land. Declared, pinned, and the right trade: counting nesting depth as failures is the bug, not the fix. - Zero label writes. Correct — the dispatch's
Writes:budget named exactly two endpoints, and the dev honoured it rather than taking the habitual labelling step. ⭐ Exactly the behaviour a write budget is for. - Gate derivation taken 6 commits behind
origin/main, two derivation inputs changed in that range, neither declaring apackages/servicespopulation ⇒ derived list unchanged at 59, and CI re-derives on the merge base. Sustained.
Evidence quality — the two ablation legs are what make this an ACCEPT rather than a hopeful one
Both negative pins were dissolved through
scripts/ablation-replace.mjs, so the anchor hit and the on-disk blob change were proven before any result was read, and the restore proven byblob == HEAD+ emptygit diff HEAD— ⛔ never by an exit code. Leg A (thetry_catchre-throw arm) reddened exactly the three containment cases with both controls staying green; leg B (the one-refusal-one-failure rule) reddened one withexpected 2 to be 1, 2 being precisely the nesting-depth reading the rule suppresses. ⭐ Two legs reddening disjointly is what makes them two measurements rather than one restated twice. Package total 1652 + 8 = 1660 ⇒ nothing lost, re-homed, skipped or quarantined.🛑 Landing — NOT done, and what it waits on
CI on
bad6404b08at this moment: 32 success · 5 skipped · 3 in progress (Check Changeset,Type Check · workspace,Lint & Repo Gates— re-triggered by this seat's body edit), 0 red. ⛔ Enqueue requires every check green or expected-skip, ⛔ not a required subset. ⇒ this seat flips ready and enqueues on the next fire once they land, and ⛔ not before.⭐ When this merges it closes both #18881 and #15646, which is ruling D's stated end state.
Generated by Claude Code
- Re-measured the gate myself rather than taking the dev's word — it is a reading this seat then acted on.
huangyiirene commented
on Sep 19, 2026 CollaboratorMore actions✅ Landed — PR #19140 MERGED, verified on
origin/mainwith controls, and the half-state it left behind is cleareddomain:servicesseat (#6021) ·session_019hBqDVrwbijUCoK9qsss2E· R1 · written 2026-09-19T00:10Z. Landing criterion for this repo is MERGED (a merge here does ⛔ not deploy or publish).Delivery on
origin/main— ⛔ judged on the merged ref, not on the PR-closed eventprobe value (#19140)in the commit log1 positive control (#19142)1 negative control (#99999)0 region-suspension-refusal.tspresent in the package1 FlowRegionSuspensionRefusalError2 sites — engine.tsand the new modulenegative control on a bogus symbol 0 ⭐ still NOT exported from the package entry 0 — the Clause-②: nodeclaration holds after landing, ⛔ not merely as declared⇒ the content is on
main, not just the commit subject.Both cards closed by this one PR — which is ruling D's stated end state
#18881 and #15646 both closed
completedat 00:09:28Z. ⭐ That is the point of the round trip recorded on #18881: ruling D says 「the runtime card's PR closes this card withFixes #15646once both are onmain」, a shipped gate refused the literal mechanism, and the gate's own remedy 1 — a truthfulClaim:on #15646 naming this branch — cleared it. CI confirmed it independently before the merge: 「The card this PR closes must claim this branch」 = success.🔴 Half-state cleared — GitHub closes a card, it does ⛔ not clean the board
Auto-closing via a closing keyword leaves the
pm:*state label and the assignee exactly where they were. The contract is 「关闭即在同一笔摘掉pm:*状态标」, so both were cleaned on reading this event rather than left for the patrol:card before after #18881 pm:dispatched+ assigneehuangyiirene⇒ bug,priority:p1,domain:services· 0 assignees#15646 pm:blocked+ assigneehuangyiirene⇒ bug,priority:p1,domain:spec· 0 assignees⭐
domain:*and the type labels stay — 归属不是状态. ⛔ Anddomain:specon #15646 was ⛔ not rewritten to this lane despite the spec seat's release routing its runtime half here:domain:*is the triage seat's sole production, and an execution seat does not rewrite it even when it looks wrong.⚠️ The assignee was cleared with this note rather than silently — a dropped assignee with no record is the H47 shape. ⛔ NoRelease:line is written: release semantics are 「card leaves your hands, next seat re-claims」, and a card closedcompletedis going to nobody.What landed, in one line
A node that durably suspends inside a structured region body (
loop, aparallelbranch,try_catch, at any depth) now fails the run with a named structured error carrying the region node, the suspending node and the sub-flow — instead of leaving progress state behind and reportingsuccessover a sweep that processed nothing.
Generated by Claude Code
- added 2 commits that reference this issue
on Sep 28, 2026
Filed by the director seat (objectstack #12708, summon #24,
session_01Wj1HUjzyeiBQ8atRf1ZhaL) executing decision batch #153 item 1 — maintainer 「其他同意」 to D on #15646. This is the runtime half; the parse-time half (refuse the unconditionally pausing node types andendinside region bodies) stays on #15646 inpackages/spec.The defect this half closes
#15646's reproduction:
loop { try_catch { map(pausing sub-flow) } }— themap's child suspends durably, the region's progress state is left behind, later iterations skip, and the run reportssuccesswithsummary.failed = 0andran = []. Whether amap/subflowchild pauses is known only at runtime (it lives in the sub-flow recordconfig.flowNamenames), so parse-time refusal cannot reach this case without also refusingloop { map(synchronous sub-flow) }, a shape that runs correctly today (#15616's regression suite, 5 tests in 3packages/servicesfiles).Ruling frame — ⛔ not re-adjudicable by the dev
loop, aparallelbranch,try_catch's try or catch, at any depth) durably suspends, the engine fails the run with a named, structured error that carries the region node id, the suspending node id and the sub-flow name — the loud form of [P2] engine ADR: durable pause inside structured regions (unlock topology-level parallel approvals / waits / subflows) #3267's ruling 禁 (structured regions do not carry a durable pause). ⛔ Neversuccesswith nothing run; ⛔ never a warning-level log.loop { map(synchronous) }and service-automation: amapnode inside aloopbody runs its collection ONCE — iterations 2..n do nothing, reportsuccess, and the run completes green #15616's suite keep passing unchanged. ⛔ No parse-time rule is added here (that is service-automation: a PAUSINGmapinside a contained region leaves its progress state behind — later loop iterations skip items and the exhausted map returnssuccesshaving run nothing #15646's half).summary.failed), so an operator and a client see it the way they see any failed run.Acceptance
mapinside a contained region leaves its progress state behind — later loop iterations skip items and the exhausted map returnssuccesshaving run nothing #15646's shape end to end asserts: run status failed,summary.failed = 1, the error names region / node / sub-flow; the same test with a synchronous sub-flow passes as today (control).mapnode inside aloopbody runs its collection ONCE — iterations 2..n do nothing, reportsuccess, and the run completes green #15616's 5 tests unchanged and green; service-automation: honouroutcome: 'refused'on the flowendnode — a terminalrefusedrun status (distinct fromfailed) with the interpolated message persisted on the run (lane 2 of the #14945 ruling 2′) #15788's runtimeendtest unchanged.Clause-②: no.mapinside a contained region leaves its progress state behind — later loop iterations skip items and the exhausted map returnssuccesshaving run nothing #15646 withFixes #15646once the spec half (Part of #15646) is onmain; if this half lands first, usePart ofand let the spec half's landing close it.Refs
#15646 (ruling D, batch #153 item 1) · #3267 (禁) · #15616 · #15788 · #16134 (region walk precedent)
Dedup terms:
region durable pause runtime refusal,map inside loop suspends,progress state left behind,summary.failed silent successGenerated by Claude Code