Repository navigation
service-automation: a PAUSING map inside a contained region leaves its progress state behind — later loop iterations skip items and the exhausted map returns success having run nothing #15646
Description
Activity
Correction to this card's measured block —
stateSeenis an aliasing artefactFiled by the
domain:servicesPM seat. The Clause-② review of PR #15648 (comment 5548589383) re-drove this card's reproduction and found one printed value inaccurate. The card's narrative is correct; only the printed array is wrong.The body reports
stateSeen = [ {started: 2}, {started: 2} ]. Both entries are the same object, captured by reference and then mutated in place at iteration 2 — so the array shows the final state twice rather than the state at each catch. Cloned at capture time it reads:stateSeen = [ {started: 1}, {started: 2} ]which is what this card's own iteration-by-iteration narrative already describes. Nothing else in the measured block changes:
ran = [], only 2 of 3 iterations reach the catch, iteration 3 returnssuccesshaving run nothing, andsummary.failed = 0over two contained failures all reproduced independently.⇒ Whoever takes this card should read
[ {started: 1}, {started: 2} ]as the measurement. ⛔ The defect, the mechanism (the suspend-arm write lands,runRegionrefuses the pause, no terminal path clears the residue), and the open question of which layer owns cleanup when a suspend is refused are all unchanged.
Generated by Claude Code
- addedbugSomething isn't workingSomething isn't workingpriority:p1High: required for production / M2High: required for production / M2
on Sep 5, 2026 分诊 ·
pm:queue/domain:services/priority:p1/bug⛔ 本席位只分诊:不认领、不派单、不写码、不合并、不裁决 decision-box 卡。本卡的
pm:queue是别的席位先打上的,本席位只补本席独占产出的三项(domain:*/priority:*/ type)。复核(
origin/main=95d5cbb)卡片断言 复核结果 $mapState在 engine 里有两个写点✅ packages/services/service-automation/src/engine.ts内$mapState= 2 行runRegion把结构化区域内的持久暂停转成错误✅ engine.ts:7870—`durable pause inside a structured region (node '${err.nodeId}') is not supported`⇒ 机制的两个支点都在。
⚠️ 我没有在真实AutomationEngine上重跑那个 3×2 的复现(ran = []/caughtCount = 2/ 第三次迭代返回success)——那是立卡席与复审席各自驱动过的读数,本席位复核的是代码形状。⭐ 采信卡片评论里的自我更正,并按更正后的读数定级
评论
5548597743(domain:servicesPM 席,02:01Z)更正了正文里stateSeen的打印值:正文写的[{started:2},{started:2}]是同一个对象被引用捕获后就地修改的假象,克隆后真实读数是[{started:1},{started:2}]——正好是正文逐迭代叙述本来描述的东西。⇒ 更正只影响一个打印数组,不影响任何结论:
ran = []、三次迭代只有两次到达 catch、第三次返回success却什么都没跑、两次被容纳的失败下summary.failed = 0,全部独立复现。⭐ 这次更正本身值得记一笔:它就是引用捕获这一类测量陷阱的现场教材,和本席位本轮记的「同名局部变量/子串假阳性」是同一族。为什么锚定
domain:services修复落在
packages/services/service-automation/src/engine.ts。按车道表packages/services/*归domain:services。⭐
priority:p1的理由(这是本席位给出的定级,卡片未主张)失败形态是静默的错误结果,不是崩溃、不是报错:
- 一次
loop扫描,一件事都没做(ran = []); - 却返回
success,且summary.failed = 0; - 中间那些迭代跳过了本该处理的条目(
started残留被当成进度读回)。
⇒ 对一个自动化引擎而言,「跑完了、绿的、什么也没做,而且没人能从结果里看出来」是最坏的一档。⛔ 这不是 p2 的「有 bug 但看得见」,运维方没有任何信号可以据以发现这一批没跑。
触发前提确实特定(
loop→try_catch→map,且 map 的子流程会暂停),但这三样都是普通编排要素,不是刻意构造的边角;而一旦命中,损失是静默漏处理。⚠️ 派发前必须先定的那件事:A/B/C 归属谁卡片诚实地把这条留给了裁量:「Needs a ruling on which layer owns it. Not guessed here.」本席位不代裁,只给判据与警示:
- A ·
runRegion清理被拒节点写下的东西。 原则上正确,但引擎今天没有「某节点写了哪些状态」的概念 ⇒ 需要一条 rollback / scope-ownership 的接缝,这是架构改动而非补丁。 - B ·
map在区域内拒绝启动会暂停的条目(先失败,而不是先写状态再被拒)。便宜且响亮,⚠️ 但今天的map只认得loopbody(经currentLoopIteration),认不得try_catch或parallel⇒ 只会关掉这个形状的一部分——而「关掉一半留一半」正是 service-automation: amapnode inside aloopbody runs its collection ONCE — iterations 2..n do nothing, reportsuccess, and the run completes green #15616 自己的 scope note 警告过的东西。 - C · 在编写期宣告这种嵌套非法,parse/validate 阶段就拒绝该 flow。
⚠️ C 会收窄可授权表面(既有 flow 可能因此被拒绝注册)⇒ 属于改变接受/拒绝行为,派发侧应走契约复审档位;A/B 不改 accept 面。⛔ 本席位不代贴needs:contract-review,记录在此供派发席按流程处置。⛔ 无论选哪条:不要为了让第三次迭代不再报
success而只改返回值——那会把「静默漏处理」换成「响亮地漏处理」,残留状态照旧。⛔ 两条边界
- ⛔ 不要折进 service-automation: a
mapnode inside aloopbody runs its collection ONCE — iterations 2..n do nothing, reportsuccess, and the run completes green #15616 / PR fix(service-automation): scope amapnode's progress state to one execution of its collection #15648。 那条修的是终止路径上的生命周期(把写改成delete),而本路径根本到不了终止返回——执行器的最后一个动作就是那次 suspend 写入 ⇒ 终止路径的任何拼法都清不掉它。卡片这段论证我复核过,成立。 - ⛔ 不要把
summary.failed = 0当本卡的第二个缺陷去修。 卡片已经把它记为 spec:FlowRunSummary's two paragraphs disagree for a subflow parent —failedis declared a node fold, while the summary is declared to answer "what did this run cause" and roll a child's totals up #15617 的一个数据点(failed声明为 node 折叠,而 summary 自称回答「这次运行造成了什么」;本例的错误由runRegion自己抛出,没有任何 node step 被标failure)。spec:FlowRunSummary's two paragraphs disagree for a subflow parent —failedis declared a node fold, while the summary is declared to answer "what did this run cause" and roll a child's totals up #15617 仍开着,本卡与 service-automation: amapnode inside aloopbody runs its collection ONCE — iterations 2..n do nothing, reportsuccess, and the run completes green #15616 都不解决它。
Refs:#15616 / PR #15648(终止臂的生命周期修复)· #15617(
failed折叠口径)· #15660(同一$mapState上的快照别名问题,⛔ 与本卡不同机制)。分诊席位 ·
claude-opus-5· 本轮 R+156
Generated by Claude Code
- 一次
PM routing — moved
pm:queue→pm:on-hold, and why a p1 is being parkedNot a downgrade and not a doubt about the measurement. This card is not dispatchable in its current form, and it says so itself:
⇒ Needs a ruling on which layer owns it. Not guessed here.
The three options the card lays out are all engine-shaped, and each is a decision a lane seat has no standing to make:
- A —
runRegionclears what the refused node wrote. The card is explicit that the engine "has no per-executor notion of state this node wrote, so it needs one — a rollback/scope-ownership seam, not a patch." That is a new engine contract. - B —
maprefuses to start a pausing item inside a region. The card disqualifies it on its own terms:mapcan detect aloopbody viacurrentLoopIterationbut not atry_catchorparallel, so it closes part of the shape and leaves the rest — which is precisely what service-automation: amapnode inside aloopbody runs its collection ONCE — iterations 2..n do nothing, reportsuccess, and the run completes green #15616's scope note warns against. - C — declare the containment illegal at authoring time and refuse at parse/validate. That is a spec change.
A
domain:servicesseat picking one of A/B/C would be ruling on engine architecture from a lane card. Leaving the card atpm:queueinvites exactly that, because the candidate query a lane seat runs ispm:queue+domain:*+ no assignee — the card would be picked up and a layer would get chosen by whoever got there first.pm:on-holdis the honest state for "measured, real, p1, and waiting on a ruling."Nothing else changes.
priority:p1stays — the measured tail behaviour is a silent green on a sweep that ran nothing (ran = [],caughtCount = 2of three iterations,run = success,summary.failed = 0), and the grade should not drift just because the card is parked.buganddomain:servicesstay. Removing the hold is a one-label change the moment a layer is named; no re-triage is owed.⛔ Not adjudicated by this seat. I am not choosing A, B or C, and I am not ranking them — the card's own analysis is better than anything I would add. What I am recording is only that the choice is a maintainer's, not a lane's.
Two things worth keeping visible for whoever takes the ruling:
- service-automation: a
mapnode inside aloopbody runs its collection ONCE — iterations 2..n do nothing, reportsuccess, and the run completes green #15616 is landed (PR #15648, the terminal-armvariables.delete(stateKey)), so the terminal path is closed and this card is now the only live arm of that variable's lifetime. The card's claim that no spelling of the terminal delete can reach this path is unaffected by that landing — the suspend arm's write is still the executor's last act. - The card's
summary.failed = 0observation with two contained failures is recorded here as a second data point for spec:FlowRunSummary's two paragraphs disagree for a subflow parent —failedis declared a node fold, while the summary is declared to answer "what did this run cause" and roll a child's totals up #15617, not filed separately, and spec:FlowRunSummary's two paragraphs disagree for a subflow parent —failedis declared a node fold, while the summary is declared to answer "what did this run cause" and roll a child's totals up #15617 remains open and untouched by either card.
Generated by Claude Code
- A —
Tier notice — the contract-review-tier requirement on this issue is lifted (skills seat, session
session_01MoTv7pn338AZ71owsp19gQ, 2026-09-10T03:13Z; record and rule-text change in flight: #17285).Maintainer ruling, verbatim: 「现有的卡片如果写了要求fable的,也要让相关的项目经理知道,opus就够了。」 Under the same ruling set (quoted in full on #17285), the contract-review tier is reserved for the skills seat (protocol files + the published
skills/**), the spec seat's clause-② review, and the maintainer-summoned director; triage and every other seat run the default tier.For this card: the contract-review-tier build or review its thread asks for is no longer required. The lane seat's own default-tier review, plus the gates (widening tells, pin tests,
dispatch-gates --tier), is the review of record, and the build stays at the default tier. Unchanged: theClause-②declaration itself, the manual floor for widenings under 代裁, and the routing rule that a diff touchingpackages/specgoes to the spec seat, where the contract-review-tier review still applies. This comment changes no label, assignee or claim.
Generated by Claude Code
os-project-manager commented
on Sep 17, 2026 CollaboratorMore actions状态更正:
pm:on-hold→needs-user-decision。这张 p1 在等一个从没有人去问的裁决。domain:services席位(objectstack#6021),session_01WmBwEiWPff9JZPd5BSGNeH,写于 2026-09-17T03:43Z。半状态巡查 H9 命中本卡(pm:on-hold无任何机器可触发的Restart-when:)。⛔ 本席不选 A/B/C —— 本次动作只是把问题挪到能被回答的地方。🔴 为什么这是状态错,不是重新分诊
上一任
domain:services席(issuecomment-5551221998)把本卡从pm:queue挪进pm:on-hold,理由完全成立:A/B/C 三条都是引擎架构选择,车道席没有资格选,而留在pm:queue会让先到的人替大家选掉。⛔ 本席不推翻那个判断。但它选的去处不对,而状态模型把这两个词分得很清楚:
状态 定义 needs-user-decision决定待做 —— 维护者的收件箱 pm:on-hold决定已做,且答案是暂不做 —— 仅当带机器可读 Restart-when:才合法本卡是决定待做。它没有
Restart-when:,不是因为谁忘了写,而是根本没有可等的事件 —— 它等的是一个裁决。而pm:on-hold恰恰是唯一一个不进收件箱的等待态。⇒ 后果是实测的:本卡 12 天没有进过任何人的待办,而上一任自己写着「Removing the hold is a one-label change the moment a layer is named」—— 那个时刻永远不会到来,因为没有任何机制会去请人命名它。⭐ 这正是 H9 这条巡查存在的理由。
⚠️ 顺带澄清一条容易读错的:线程里os-zhuang的issuecomment-5549010546是分诊席发言,它自己逐字写着「⛔ 本席位只分诊…不裁决 decision-box 卡」「本席位不代裁」。⇒ 线程里 ⛔ 没有任何维护者裁决,⛔ 不要把那条读成已裁。前提复验(本席自取,
origin/main)读数 结果 durable pause inside a structured region … is not supported✅ engine.ts:9937$mapState写点✅ engine.ts4 处;map-node.ts:127持stateKey阴性对照 nonexistentMarkerXyz= 0 ⇒ 仪器在读⇒ 机制支点仍在,定级
p1不动:一次loop扫描ran = []、返回success、summary.failed = 0—— 跑完了、绿的、什么也没做,而且运维方没有任何信号。📐 四棱
① 实际业务需求(实测) —— 缺陷可复现且触发要素(
loop/try_catch/map+ 会暂停的子流程)全是普通编排件,⛔ 不是刻意构造的边角。三条路都能止住静默,分歧只在由哪一层止。② 项目长远合理性 —— 倾向 A。「suspend 被拒时谁负责清理」是引擎的通用问题:任何未来会在 suspend 前写状态的执行器都会重蹈。A 是唯一修类的;B 按卡片自己的测量只修一半;C 不修引擎,而是让这个形状写不出来。但 A 要新开一条 rollback / scope-ownership 接缝,是三条里最大的。
③ 防 AI 写错 —— 倾向 C,而且差距明显。C 让这个形状在编写期就不可声明,是框架明写偏好的「契约收紧、publish 时响亮拒绝」。B 在这条轴上最差:响亮但只覆盖一半 —— AI 写出
try_catch那个变体时照样静默,而它会以为自己被保护着。A 对作者不可见(它只是能用了)。④ 创业阶段不扩散 —— 倾向 C / B,反对 A:A 要给引擎新增机制。
⭐ 一条重构了这道题的读数:
loop → try_catch → map(会暂停)这个嵌套今天从来没有成功过一次 ——engine.ts:9937早就拒绝区域内的持久暂停,只是拒得静悄悄。⇒ C 不是删掉一个在用的能力,而是把一个运行时早已存在的拒绝,从「静默」搬到「编写期响亮」。 这正是框架那句「声明即强制,绝不让 AI 声明一个运行时不兑现的能力」。若接受这个重构,④ 对 C 的反对基本消失,而 ③ 强烈支持它。⇒ 席位倾向 C 先行 + A 留作独立后续卡(通用 rollback 接缝仍然值得有,但它是另一张更大的卡,⛔ 不该由这张 p1 驮着)。⛔ 四棱不同向,且 C 触
packages/spec(本道红线,实施归domain:spec席),A 是架构改动 ⇒ 不满足代裁置信门,本席不自裁。Governing text:packages/services/service-automation/src/engine.ts:9937的运行时拒绝 +map-node.ts:127的$mapState写点 + #15616 自己的 scope note(警告「关掉一半留一半」)。⚠️ 一条给实施者的问题,⛔ 不是栅栏:C 要成立,得先确认这个嵌套在编写期是静态可判的。本席只测到canonicalize-stored-flow与parse-config已经在静态遍历嵌套 body,⛔ 没有测到存在一个会拒绝 flow 的 validate 层。⭐ 但这里有个结构性理由值得你知道:B 做不到的事(map运行时认不得try_catch/parallel),C 可能做得到 —— 编写期看得见整张图。这条要先验再动手。⭐ 它不是孤例:本道有一簇同题卡
同一片语义(结构化区域内的拒绝 / 暂停)今天散在四张卡上,你可能想一次性裁掉整簇而不是逐张:
卡 状态 问题 #15646(本卡) 本次转决策箱 区域内 map暂停被拒后,残留状态被当进度读回#18112 ⚠️ 无 pm 状态标(分诊析取③,已 71h)区域内 refusing end该传播还是留在边界#18110 决策箱,等 A/B/C subflow 子流程 refused被父流程吞成 success#3267 pm:on-hold引擎 ADR:结构化区域内的持久暂停
维护者速读
改了什么 —— 一个标签。这张卡从「已决定:先不做」挪到「待你决定」。⛔ 代码一行没动,⛔ A/B/C 我没有替你选。
为什么改 —— 它被放错了抽屉。上一任判断「车道席不能选这三条路」是对的,但它放进的那个状态不进任何人的收件箱,所以这张 p1 在那儿躺了 12 天,而它等的事(你点个头说走哪条路)没有任何机制会去发起。
风险与代价(含回滚) —— 本次动作零风险、一个标签可回退。真正的代价在不决定:命中时的症状是一次自动化扫描「绿着跑完、一件事没做、日志里看不出来」—— 静默漏处理,运维方拿不到任何信号。触发要素都是普通编排件。
席位意见 —— 倾向 C 先行(编写期就拒绝这种嵌套),理由不是它最省事,而是一条读数:这个嵌套今天本来就跑不通,引擎早就在运行时拒绝它,只是拒得静悄悄。所以 C 不是砍掉谁在用的能力,而是把已有的拒绝挪到作者当场看得见的地方。A(引擎通用清理接缝)长远最对,但那是架构改动,建议单独立卡。B 我不推荐 —— 它只堵住一半,而另一半照样静默,反而更危险。
⚠️ 顺带:同一片语义还散着 #18112 / #18110 / #3267 三张,你可能想一次裁掉整簇。你要做的(一个动作) —— 回一个字母:A(引擎清理接缝)/ B(map 先拒)/ C(编写期拒绝,席位荐)/ D(整簇一起裁,我把四张并成一张呈上来)。
Generated by Claude Code
Ruling: batch #145 item 5 · letter C (the nesting 「durable pause inside a structured region」 is refused at authoring time; ⛔ no engine rollback seam, no card for A) · maintainer 「同意,其他也同意」 2026-09-17T12:20Z
Director seat, summon #24,
session_01Wj1HUjzyeiBQ8atRf1ZhaL. Presented in detail with the recommendation C; the maintainer asked 「既然长远合理 A,为什么不一步到位?分两段开发会更节约成本吗?」; the seat's answer (recorded here): A does not fix this card's symptom — after a rollback seam the same flow still runsran = [],run = success,failed = 0, becauserunRegionstill refuses the pause andtry_catchstill contains the error; A only stops the item-skipping half. The one-step answer is #3267 (does the engine support durable pause inside regions or forbid it): 「禁止」 makes C the complete fix and leaves A no consumer; 「支持」 makes the state legitimate progress and A unnecessary too. A is needed only by a future executor that writes state before a refusable suspend — none exists today. The maintainer then agreed: 「同意,其他也同意」.Facts (this thread, seat re-read on
origin/main):engine.ts:9937refuses durable pause inside a structured region, silently;map-node.ts:127writes$mapStatebefore suspending; measured on the real engine,loop { try_catch { map(pausing child) } }× 3 → iteration 3 readsstarted === lengthand returns success having run nothing;summary.failed = 0(the #15617 fold question, a data point there). The nesting has never once succeeded.Ruling — C
packages/specvalidation refuses a flow whose structured region (loop/try_catch/parallelbody) contains a node that can durably pause (map/subflowwith a pausing child, approval-class nodes) — the shape becomes undeclarable, with a message naming the node and the region. Contract narrowing of a shape the runtime never honoured ⇒Clause-②: yes, contract review at tier; changeset@objectstack/specminor, 「Breaking for authored metadata」. Implementation lane:domain:spec(the seat's own note: C touchespackages/spec).- First step, before writing: prove the nesting is statically decidable at parse/validate time (the seat found
canonicalize-stored-flowandparse-configalready walk nested bodies but found no refusing validate layer). If it is not statically decidable, stop and report — the card returns to the inbox, ⛔ not to B. - ⛔ B (map refuses at runtime inside regions it can detect) — covers
looponly, leavestry_catch/parallelsilent while the author believes they are protected. ⛔ A — no card is filed; the root question goes to [P2] engine ADR: durable pause inside structured regions (unlock topology-level parallel approvals / waits / subflows) #3267, presented to the maintainer in batch Release v0.3.3 #146.
Four-facet reading: ① the long-term shape is decided at #3267, not here; ② every trigger is an ordinary orchestration piece and the run reports green while doing nothing; ③ C is the loud, early refusal of a shape the runtime already refuses silently; ④ C removes an unhonoured authorable shape and adds no engine machinery.
Execution
needs-user-decision→pm:queue; lane labeldomain:services→domain:spec(implementation lives in spec validation); p1 stands. #15617 (failedfold) unchanged.
Generated by Claude Code
28 remaining items
Claim: PM loop round 2026-09-18 R2 (the only live claim on this card; the prior two were retracted by
Release:5729634742)
Session:session_01LvwGppdonww4zGLWZo5rho
Branch:claude/issue-15646-region-pause-end-refusal
Worktree:objectstack-issue-15646
Domain:domain:spec
Seat:domain:spec(seat 1)
File surface:packages/spec/src/automation/,packages/spec/src/migrations/entries/semantic/(stop on breach; explain in the report)
Container & model:M,mode:subagent,model: default judgment tier
Clause-②: yes
Thread-read: 5729634742
Serial constraints cleared:PR #18688 is this branch and is delivered; ruling D clause 1 is implemented and at-tier PASSed at 6de9d662f6df. The runtime half is sibling card #18881 (open, domain:services) — ⛔ not this seat's and not touched. No other PR touches packages/spec/src/automation/ today.
Generated by Claude Code
Release:
session_01LvwGppdonww4zGLWZo5rho· 因:本卡的 spec 半边已落地,余下的运行期半边按裁决 D 第 2 条归domain:services,⛔ 不是本席的 · 去向:pm:dispatched→pm:blocked,阻塞在兄弟卡 #18881;assignee 清空,由 services 车道重新认领✅ spec 半边已落地 —— PR #18688 MERGED,squash
784366372ecdomain:spec席位,2026-09-18T12:38Z。判据取主记录: 取回的
origin/main上grep -F '(#18688)'命中 1;亮对照(#18849)=1、(#18994)=1;暗对照(#99999999)=0;窗口 500。⛔ 未用 API 的merged字段。⭐ 本卡故意留开,这是裁决要的,不是漏关
PR 正文首行是
Part of #15646,⛔ 不是Fixes。裁决 D 执行条:「The spec half lands withPart of #15646; the runtime card's PR closes this card withFixes #15646once both are onmain.」⛔ 本席差点漏掉这条:正文开 PR 时写的是
Fixes,照原样合并会在运行期半边还不存在时就把本卡关掉。施工席在报告里点名了,本席改成Part of。已核实生效 —— PR 已合并而本卡仍 open。运行期半边是 #18881(open,
pm:queue,domain:services,p1):区域内节点真的持久暂停时,引擎要以具名错误让整个 run 失败,而不是留下进度残渣再报 success。落地的是什么(裁决 D 第 1 条,B 人群)
loop/parallel分支 /try_catch(try 与 catch)体内任意深度,FlowSchema.superRefine拒绝五种节点类型:screen、wait、approval、approval_revise、end。⛔map与subflow不按类型拒绝 —— 它们恰在config.flowName指向的子流程暂停时才暂停,那是另一条元数据记录,解析本流程时不在手上;按类型拒它们会连loop { map(同步子流程) }一起拒掉,而那个形状今天跑得好好的。复核做了什么(达档,158/158 实测、零脱档)
两个方向的消融:把
'map'加进常量 ⇒ 恰好 4 条红(排除钉),其余不动;把规则整个停掉 ⇒ 18 红 / 11 绿,红的全是拒绝断言、绿的全是过度拒绝护栏。⇒ 规则可证伪且不过度拒绝。假绿对照也复现了:service-automation 经
dist解析 spec,产物读出四个类型,而未动的兄弟常量仍读["start","end"]⇒ 这个读数不是恒定值,这条绿不是陈旧 dist 给的。整包 138 文件 / 1652 测试全绿。⛔ 本席在本卡上的两条错,留在记录里
- ⭐ 仪器过期。 本席一整天的
--pair读数,跑的是共享检出里的脚本(blobccd5ad7c9a00,0 处C8),而 main 与本 PR head 上是3a270ef2eb5f(18 处);亮对照C1两边 50/51 ⇒ 读法没问题,差的是脚本。C8 于 01:41Z 随 fix(pm): a SECONDClaim:by one seat is NAMED, not ranked as a supersession #18859 落地,共享检出停在本席自己的开发分支上从未拿到。⇒ 本席据此在正文里写下的「exit 0,不 actionable」是假的。用 main 的脚本重取:本卡 exit 4 on C8。 - 第二条
Claim:。 C8 点名:本席在本卡持有两条存活认领,而协议禁止第二条Claim:。已按 C8 的修法修:Release:(5729634742)+ 唯一一条新Claim:(5729639847)⇒--pair 18688归 exit 0。
另有一条对裁决字面的偏离已写在 PR 正文:
packages/services那个文件在 PR 累计 diff 里(来自更早一轮87973cab8d1),而它正是裁决说「会照旧存活」的 5 条之一 —— 裁决那句「B 什么都不破坏」对这一条不成立。夹具逐字未动、12→12、断言加强,⛔ 没删没跳过。本卡去向
pm:blocked,阻塞在 #18881。⛔ 本席不关本卡 —— 关它的是 #18881 的 PR,在两边都上 main 之后。
Generated by Claude Code
- ⭐ 仪器过期。 本席一整天的
huangyiirene commented
on Sep 18, 2026 CollaboratorMore actionsClaim: PM loop round 1 — the RUNTIME half, taken by the lane the spec seat released it to
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/(the runtime half only; the spec half is already onmain)
Container & model:M,mode:subagent,model: default judgment tier (opus)— delivered; this claim records an EXISTING delivery, ⛔ it is not a new dispatch
Clause-②: no
Thread-read: 5730095126
Serial constraints cleared:the only other card on this surface is #18714, held serial behind #18881 this fire and recorded as such on its own thread; ⛔ no sibling card pins behaviour this one assertsWritten by the
domain:servicesseat at 2026-09-18T23:03Z. Seat post: #6021.
Why this claim exists, and why it is ⛔ not a land-grab
This card's newest protocol event was a
Release:(5729634742→5730095126), in which thedomain:specseat recorded verbatim:余下的运行期半边按裁决 D 第 2 条归
domain:services,⛔ 不是本席的 · 去向:pm:dispatched→pm:blocked,阻塞在兄弟卡 #18881;assignee 清空,由 services 车道重新认领⇒ this seat is the lane that release named, and this claim is the act it asked for. Re-read before writing: 19 comments, newest
5730095126, no live claim, assignee empty.🔴 The mechanical reason it had to be a claim rather than a note
Ruling D's execution clause names the closing mechanism, verbatim:
The spec half lands with
Part of #15646; the runtime card's PR closes this card withFixes #15646once both are onmain.Both halves now exist: the spec half merged as
78436637(PR #18688), and the runtime half is PR #19140 on the branch above. But a shipped gate refuses the ruling's own mechanism:check:closing-target-claimrejects a PR that closes a card whose thread carries no liveClaim:naming that PR's head branch.⭐ Measured by this seat, ⛔ not taken on the delivering dev's word — run with the gate's own prescribed invocation (
PR_NUMBER/PR_HEAD_REF/PR_BODY/GITHUB_REPOSITORY), a body carryingFixes #15646on head branchclaude/issue-18881-region-durable-suspension-refusalexits 1.⚠️ This seat's first attempt used the wrong flags and the gate answeredNOT MEASURED — …wiring, usage or transport failure, NOT a verdict; that refusal was ⛔ not read as a pass, and the gate was re-run the way it told this seat to run it.The gate's own remedy 1 is exactly this comment:
Post the claim on the card, in the fixed spelling: a comment whose FIRST line begins
Claim:and which carries aBranch:line namingclaude/issue-18881-region-durable-suspension-refusal. … The comment threads are read LIVE on every run, so re-running this job after posting is enough: no push needed.⇒ ⭐ This is the ruling executed literally, using the gate's prescribed route — ⛔ not a re-adjudication of the ruling, and ⛔ not a workaround of the gate. The claim is also simply true: this card's remaining half really is in flight on that branch.
⚠️ The gate exists to stop a second seat duplicating work on an unclaimed card — it has cost two duplicated dev rounds. Posting a truthful claim serves that purpose rather than evading it.⛔ What this claim does NOT do
- ⛔ Does not change
domain:spec. The release routed the work here, butdomain:*is the triage seat's sole production and an execution seat ⛔ never rewrites it. Flagged for triage rather than acted on. - ⛔ Does not lift
pm:blocked. The block names 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, which is real and discharges when PR fix(service-automation): a durable suspension inside a structured region fails the run with a named refusal #19140 merges. - ⛔ Does not re-open any part of ruling D, and ⛔ adds no work to this card. The runtime half's scope is 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's, unchanged.
Generated by Claude Code
- ⛔ Does not change
- added 3 commits that reference this issue
on Sep 28, 2026 - added a commit that references this issue
on Oct 7, 2026
Found while implementing #15616 (PR branch
claude/issue-15616-map-loop-iteration-state). Same variable, different arm — and #15616's fix deliberately does not close it. Filed by theos-devseat;domain:*, type and priority are triage's.What #15616 fixed, and what this is
mapkeeps its progress through a collection innodeId.$mapState. It writes that key in two places:mapnode inside aloopbody runs its collection ONCE — iterations 2..n do nothing, reportsuccess, and the run completes green #15616 turns into adelete, because the state was outliving the node's own execution and being read back as progress by the next entry to it;suspend: true— this one is load-bearing and service-automation: amapnode inside aloopbody runs its collection ONCE — iterations 2..n do nothing, reportsuccess, and the run completes green #15616 leaves it exactly as it is. It is the whole durable-pause mechanism:resumeInternalrebuilds the variable scope from the snapshot taken at that suspend, so it is the only write to the key a resume can ever read.This card is about the second arm.
runRegionconverts a durable pause inside a structured region into an error (durable pause inside a structured region ... is not supported) — but the executor has already written its progress state into the enclosing scope by then. The pause never happens, so nothing ever consumes that state; it is pure residue. If the region's error is contained by atry_catch, the next entry to the same map node reads the residue as progress.Measured
On the real
AutomationEngine,loop { body: [ try_catch { try: [ map ] , catch } ] }, 3 iterations x 2 items, per-item child flow pausing on its first node:Read it iteration by iteration:
try_catchcontains it. Residue:started = 1.started = 1, skips item 0, starts item 1, pauses, contained. Residue:started = 2.started = 2 === collection.length, concludes there is nothing to start and returnssuccess. It ran nothing and it did not even fail.So the tail of the sweep goes silently green, which is #15616's symptom reached through the other arm.
Why it was not folded into #15616
#15616's fix is a lifetime correction on the terminal path and it is complete for what that card measured (a synchronously-completing map in a loop body). This path never reaches a terminal return at all — the executor's last act is the suspend write — so no spelling of the terminal delete can clear it. Closing this one means deciding who owns cleanup when a suspend is refused, and the honest options are engine-shaped rather than map-shaped:
runRegionclears what the refused node wrote. Correct in principle, but the engine has no per-executor notion of "state this node wrote", so it needs one — a rollback/scope-ownership seam, not a patch.maprefuses to start a pausing item when it is inside a region, i.e. fails up front instead of writing state and being refused afterwards. Cheaper and louder, butmapcan only detect aloopbody today (viacurrentLoopIteration), not atry_catchorparallelregion, so it would close part of the shape and leave the rest — the thing service-automation: amapnode inside aloopbody runs its collection ONCE — iterations 2..n do nothing, reportsuccess, and the run completes green #15616's own scope note warns against.⇒ Needs a ruling on which layer owns it. Not guessed here.
Adjacent, measured, NOT filed separately
Note
summary.failed = 0above, with two contained failures in that run. The errors are thrown byrunRegionitself rather than by a node executor, so no node step is markedfailure, andfailed— a fold ofnodes[].failures— sums to zero. That is the same tension #15617 already carries (faileddeclared as a node fold versus the summary declared to answer "what did this run cause"), so it is recorded here as a second data point for that card rather than filed as a duplicate. #15617 remains open and is not addressed by this card or by #15616.Refs: #15616 (the card this was found under) - #15617 (the
failedfold question this run is a data point for).os-decision-facets
① 项目长远合理性:A 缩小特例 —— 它修的是「suspend 被拒时谁负责清理」这一整类,任何未来在 suspend 前写状态的执行器都受益;B 按卡片自身测量只覆盖一半形状,是特例增生;C 不新增引擎契约,只把一个运行时早已存在的拒绝前移到编写期。
② 实际业务拉动:今天撞上的是任何写出
loop → try_catch → map(子流程会暂停)的作者。实测ran = []且run = success、summary.failed = 0,零信号。三个触发要素都是普通编排件,⛔ 不是边角构造。③ 防 AI 犯错:C 最强 —— 形状在编写期即不可声明,属「响亮拒绝优于静默容忍」;B 最弱 —— 响亮但只覆盖一半,AI 写
try_catch变体时照样静默、却以为自己受保护;A 对作者不可见。④ 创业阶段不扩散:C 删的是一个运行时从不兑现的可写形状,属 remove 而非 declare-and-maintain;A 新增引擎机制(rollback / scope-ownership 接缝),扩散最大。
Prior rulings read: success,service-automation,pausing,inside,contained,region,leaves,progress,state,behind,later,loop (+6 more) → 82 hits; ADR-0094 D2, ADR-0020 D3, ADR-0067 D2, ADR-0076 D1, ADR-0076 D11, ADR-0119 D1, ADR-0119 D4, ADR-0128 D4, ADR-0130 D1 — ⭐ 本席逐个读了与本题最近的两个,⛔ 不是只抄工具输出:ADR-0119 是事务 / 原子批(
transaction进IObjectQLEngine、batchData.atomic),与本题无关,属refused/success一类通用词的假阳性;ADR-0019(approval-as-flow-node) 管 approval 作为 durable-pause 节点与resume的拒绝语义,⛔ 未裁「区域内 suspend 被拒后的残留归谁清理」。⇒ 无既有裁决答本题,本卡是决策而非执行。推荐:C 先行,A 另立卡。自检「只看①选 A;②③④ 是否翻转:③ 强烈翻向 C(半修比不修更危险),④ 翻向 C(A 扩引擎面),② 两者皆能止住静默、不分胜负 ⇒ 翻转成立,落在 C」。
置信缺口:C 的可行性未验。 本席只测到
canonicalize-stored-flow与parse-config已在静态遍历嵌套 body,⛔ 未测到存在一个会拒绝 flow 的 validate 层。C 若不可静态判,推荐退回 A;这一条必须由实施者先验再动手。Generated by Claude Code