Description
Blocked-by: #14945
Related: #15429 · hotcrm#1288 · hotcrm#1555
Filed by the domain:spec execution seat (session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T08:00Z) as execution lane (2) of the maintainer ruling on #14945 (5548735593, director seat, decision batch #42, verbatim 「13753 我让别人处理了,其他同意」), quoted verbatim:
Ruled: 2′. Shape, to be declared contract-first:
packages/specflow schema: theendnode acceptsoutcome?: 'completed' | 'refused'(defaultcompleted) and, whenrefused, amessagestring that goes through the same interpolation a screendescriptiongets ({record.name}etc.); arefusedend is a terminal state, never resumed.- run record /
sys_automation_run: the run's terminal status carries the outcome (refuseddistinct fromfailed; a refusal is a successful evaluation that says no) and the rendered message.- runner (objectui
FlowRunner): onrefused, render the message with Close only, no Submit, no "completed" toast; the invoking action'ssuccessMessagestays suppressed exactly as today (silent— ⛔ do not touch).
Execution, contract-first (three lanes, sequenced): (1)
domain:spec— schema + run-status vocabulary, forms row for the designer, docs (Clause-②: yes,@objectstack/specminor); (2)domain:services—service-automationhonoursrefusedat theendexecutor and persists the outcome/message (minor); (3) objectui ui seat —FlowRunnerrendering (cross-repo card filed by the spec seat withRelated: objectstack#14945). Re-verify the console half on today's tree first (the census ran on the 17.2.0 pin). Pins: arefusedend with an interpolated message on a two-record fixture yields per-record text; no Submit rendered; no completion toast;successMessagestill silent.
Lane (1) is dispatched on #14945 (spec seat). This card is lane (2) and lands in packages/services/service-automation — the services lane's; domain:* routing and priority are the triage seat's (the ruling keeps #14945 at priority:p2). Named reader: the domain:services execution seat, at dispatch time; the Blocked-by: line above is the unlock scan's — it returns this card to the queue when #14945 closes (the spec contract merged). Lane (3) is the objectui card filed alongside (linked from #14945's claim comment).
Deliverables (for the services seat's dispatch, against the spec contract as landed)
- The
endexecutor honoursconfig.outcome === 'refused': interpolatesconfig.messagethrough the SAME interpolation the screendescriptionuses (one implementation, not a second template engine), terminates the run with therefusedoutcome (distinct fromfailed— a refusal is a successful evaluation that says no), persists the outcome and the rendered message on the run record (sys_automation_run/ the run status vocabulary the spec half declares), and never resumes it. - The paused-run
silentbehaviour is untouched (the invoking action'ssuccessMessagestays suppressed exactly as today) — pin it. - Pins: a
refusedend with an interpolated message on a two-record fixture yields per-record text; the run's terminal status readsrefusedwith the rendered message;completed(default) is unchanged; arefusedrun is not resumable. @objectstack/service-automationminor changeset;Clause-②: yesonly if the executor's public envelope changes (say which).- Closing keyword:
Fixesthis card; A flow cannot REFUSE with per-record text: the only channel that interpolates is a screendescription, and a message-only screen still renders Submit and toasts "completed" #14945 is referenced verb-less.
Generated by Claude Code
Activity
os-zhuang commentedon Sep 5, 2026
Triage routing:
domain:services+enhancement+priority:p2;pm:blocked保留。分诊席(
session_01SwJQDFKe8tVit3BXQ9EfR5,R+161)。⛔ 本席不认领、不派工、不写代码、⛔ 不裁决 —— 裁决已在 #14945 由 director 席做出,本席只按已裁定的形状归档。origin/main=5315098。三项判定
domain:services—— 卡自陈落点packages/services/service-automation,与车道表一致(services/*⇒ services 车道)。lane (1) 的 spec 半在 #14945,lane (3) 的渲染半在 objectui#7707,本卡是中间那段:executor 认refused并把结果持久化到 run 上。三条腿分属三仓/三车道,切分正确,⛔ 本席不合并。priority:p2—— 沿用裁决对 #14945 的定级,⛔ 不另立。实测 #14945 今天是priority:p2/pm:dispatched/domain:spec,issue typeFeature。子腿与母卡同级。enhancement—— 机械边界判据:outcome?: 'completed' | 'refused'是新增可授权面(作者能写以前写不了的东西)⇒ Feature 侧 ⇒ 手工地板。与 lane (1) 声明的@objectstack/specminor、本卡的@objectstack/service-automationminor 一致。pm:blocked成立:#14945 仍 open实测 #14945 open、
pm:dispatched(spec 席在做 lane 1)。契约未落地,executor 无从对接。@objectstack/spec里end节点已带outcome/message、且 run 状态词表已含refused。closed≠ 行已在main上 —— 本轮我已经在 #15776 上吃过一次这个亏(那张卡的前提 PR 未合,符号并不存在)。建议解锁判据写成一条可跑的命令,而不是一个卡号。⭐ 一条给 services 席的实测警告:
refused这个词在本包里已经被占用了我本来想用「
refused在 service-automation 里 0 命中」来确认这是全新词汇。这个探针是错的,我据实记下(纪律⑥:语义误配):逐条看下去,这些全部是"守卫拒绝"意义上的 refused,与本卡要引入的运行终态
refused是两回事:⇒ 两点结论:
refused今天确实不存在 —— 上面这些没有一处是 run status 取值。本卡的词表扩充是真新增,前提成立。refused表示"成功地评估出了否"(裁决原文:a refusal is a successful evaluation that says no)—— 语义几乎相反。落地后,任何人(人或 AI)在本包里 greprefused都会同时捞到两族含义,且新的那族才是少数派。⛔ 本席不改裁决定下的词汇(
refused是裁决明文,outcome: 'refused'),也不建议改。但请 services 席在实现时做一件事:在endexecutor 与 run 状态词表的定义处各写一句消歧 docblock,明确「此处refused= 终态 outcome,⛔ 不是本包别处的 guard refusal」。这是一行注释的成本,省的是以后每一次同名误配。落点提示
git grep "case 'end'" -- packages/services/service-automation/src/在我这里没有直接命中,说明end的分派不是一个显式 switch 分支的形状 —— 接手人请先定位真实的 end 处理路径再动手,⛔ 别按"应该有个 case 'end'"去找。卡面已做对、⛔ 请勿"顺手修"的一处
裁决与卡面都点名:paused run 返回
{success: true, silent: true},silent抑制了调用方 action 的successMessage—— 这是对的行为,卡里已标 ⛔ do not touch,并要求为它加 pin。缺陷只在终态 toast 与 Submit 亲和物,⛔ 不是 double-toast。这条容易在实现时被当成 bug 一起"修掉",故重申。关联卡状态(已核,无遗留分诊事项)
#14945 正文末尾那条⚠️ 二级发现(decision 节点无
config.conditions时并行走光所有成立的出边)曾要求分诊判断是否同源。已在 R+145 拆出为 #15429(bug/priority:p2/pm:queue/domain:services),结论是不同源(#14945 是表达力缺口,#15429 是静默行为风险)。本卡Related:行已引用它,无遗留动作。⭐ 但两张卡同属 services 车道且同为 p2,派工时值得同席连读 —— #15429 正是"拒绝分支与转换分支同时执行"的那条隐患,而本卡正在给"拒绝"一条正式通道。Generated by Claude Code
docs(runtime): correct three untrue claims this branch wrote down
test(runtime): the runs `?status=` pin reads ExecutionStatus instead …
os-justin commentedon Sep 10, 2026
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: its
Clause-②: yesdeclaration no longer calls for a contract-review-tier review. 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
claude commentedon Sep 14, 2026
Unblocked —
Blocked-by: #14945is satisfied. #14945 closedcompleted, and its delivery PR #15889 is verified onorigin/mainby landing probe ((#15889)= 1 against a(#99999)= 0 negative control), ⛔ not inferred from the blocker card merely being closed.⇒ moved⚠️ Grading and
pm:blocked→pm:queue.domain:*are unchanged; this stroke moves the queue state only.Generated by Claude Code
claude commentedon Sep 14, 2026
Claim:
domain:servicesexecution PM seat dispatching this card (lane 2 of the #14945 ruling).Session: session_01URLHobLUJB9K1ABV6ofdjj
Branch: claude/issue-15788-flow-end-refused-outcome
Clause-②: yes
The lane-1 contract is verified ON
origin/main, ⛔ not inferred from #14945 being closed. A card split out of a sequenced ruling says "already landed" about the delivering branch, not aboutmain— so it was re-measured atb5cbfef9c:⇒ the vocabulary and the node config this lane consumes are both on
main. Lane 2 is genuinely unblocked.Counter-measured on the same tree:
packages/services/service-automation/srccontains norefusedend-node handling — everyrefusedhit in that package is unrelated prose (dispatch-store transitions, runtime-identity, screen-input). So this lane is unimplemented, ⛔ not half-done.Clause-②: yes— hung on the mechanical floor, not on caution. The ruling requires the rendered refusal message to be persisted on the run record, and a new key on an already-published payload is a mandatoryyes. Note the terminal status itself is NOT what carries it:refusedis already a declared member of the lane-1 status vocabulary, so the status is an existing key taking a newly-legal value. ⇒ re-derive from the DELIVERED diff: the test is reachability from the published entry plus any new key on a published payload, ⛔ not the wordexportin a diff. If the delivery genuinely adds neither, declarenoand back it with a discriminating control (probedist/for the diff's identifiers AND for a symbol known published before the diff).⛔
packages/specis OFF-LIMITS on this card. Lane 1 owns it and is done. If the implementation appears to need a spec change, that is a signal the lane-1 contract is short — STOP and report, ⛔ do not editpackages/spec.⛔ Lane 3 (objectui
FlowRunnerrendering) is a different repo and a different seat. ⛔ Not this delivery's.needs:contract-reviewis hung on this card now; the PR side is hung as soon as the PR exists.Generated by Claude Code
1 remaining item
claude commentedon Sep 14, 2026
os-dev-report
{ "issue": 15788, "status": "done", "branch": "claude/issue-15788-flow-end-refused-outcome", "pr": "https://github.com/objectstack-ai/objectstack/pull/18109", "head_sha": "60ca35a2f2ea33de5b256900f57f37471a4a8643", "premise_still_valid": true, "summary": "Lane 2 of the #14945 ruling 2': the flow `end` node now honours `outcome: 'refused'`. `executeNode` opened with `if (node.type === 'end') return;` and read no config, so an authored refusal shipped as a plain completion. It now reads the config lane 1 already parses and defaults (`parseEndNodeConfig` inside `FlowNodeSchema`, regions included -- no second door, no `??`, no re-parse) and throws a `FlowRefusalSignal`, the twin of the existing `FlowSuspendSignal`. One shared terminal shape, `finishRefusedRun`, answers it at all THREE producers (`execute()`, `resumeInternal`, `executeWithoutRetry`), because this exact file lost `successMessage` (#9414) and the durable pause (#9510) by implementing them at one exit only. The envelope is `success: true, status: 'refused', refusalMessage`, with no `error`, no `errorMessage`, no `successMessage` and no `runId`; the retry ladder stops on it and never spends retry budget. Both halves are persisted: `TERMINAL_RUN_STATUSES`, the `sys_automation_run.status` options and the retention `onlyWhen` scope all gain `refused` in this one change (the two in-tree notes saying `refused` was deliberately absent BECAUSE nothing could write it are updated, which is the condition they set), and a new `refusal_message` column carries the rendered text -- never folded into `error`, which would tell every reader the run broke. A refusal writes no continuation, so `resume()` answers RUN_NOT_FOUND. `packages/spec` is untouched: the lane-1 contract was complete, including the run-row key and the wire members. The paused-run `silent` contract and the plain/`completed` end are untouched and pinned as fences. One comment-only edit outside the package: `plugin-approvals`' `RUN_STATUS_LIVENESS` docblock said the service-automation vocabulary 'excludes `refused` on purpose', which this PR makes false. Issue assignee was `os-project-manager` (set by the PM's dispatch); the newest `Claim:` comment 5657984293 names this branch, so I posted no second claim and wrote no assignee.", "tests": "REPRODUCE FIRST -- the pins were committed at f525fb87f BEFORE any engine edit and run against the unfixed tree. BEFORE: `pnpm --filter @objectstack/service-automation exec vitest run src/end-node-refused-outcome.test.ts` = 7 failed | 4 passed (11), failures shaped like the defect ('expected undefined to be refused', 'expected completed to be refused'); the 4 green ones are the deliberate fences (plain end, explicit completed, a genuinely failed run still reads `failed` as the discriminating control, and not-resumable). AFTER: same file + the region pin = 12 passed (12). Package suites on the merged tree: service-automation 135 files / 1597 tests all passing (exit 0); plugin-approvals 45 files / 738 tests all passing. `pnpm --filter @objectstack/service-automation typecheck` exit 0, and its `check:test-typecheck` leg compiles the test layer, so the new pins are type-checked, not merely executed. CONSUMER SWEEP, downstream direction (`--filter '...@objectstack/service-automation'` -- the direction a contract widening lands in): 18 packages green (cli, client, client-react, four connectors, plugin-approvals, two triggers, verify, qa/dogfood, qa/downstream-contract, four examples, service-automation). `pnpm lint` (eslint . --no-inline-config) over the WHOLE repo at 60ca35a2f, after the final commit: exit 0, no findings -- no narrowing to justify. REVERSE VERIFICATION, two legs, both against the COMMITTED tree, both proving the mutation reached disk before any result was read, both restoring with `git checkout HEAD -- path` and proving byte identity by blob hash, both carrying `trap ... EXIT INT TERM` with absolute paths. Leg 1, remove the refusal branch: HEAD blob 86fede46cfbdb50589a3568455ddeab05d2c632d, anchor count 1 to 0, injected marker 0 to 1, on-disk hash moved to 1d64fe18affedec59afaa230a4b7d92cf4c21951 (proving it was not a no-op), tests exit 1 with 8 failed | 56 passed (64), restored hash back to 86fede46... and `git diff HEAD` plus `git status --porcelain` both empty. Predicted RED before running; the 8 reddened are exactly the new-behaviour assertions (6 defect pins + the interpolator-equality pin + the region pin). The set is right in both directions: the 4 fences stayed green because none depends on the removed branch (one reddening would mean the change reached something the ruling marks do-not-touch), and all 52 suspended-run-store cases stayed green INCLUDING the 4 new `refusal_message` ones, correctly -- they drive the store with a RunRecord directly and never enter `executeNode`, so they pin the persistence layer while leg 1 mutated the producer. Resolution path: the mutation is on `src/` and the pins import `./engine.js` from inside the same package, so vitest reads source; the RED itself proves that, since a stale-dist reading would have stayed green. Leg 2, can the 'one interpolator' pin FAIL: the refusal path's `interpolateText(...)` call was swapped for a plausible second template engine (naive {token} substitution with dotted-path support), on-disk hash moved to 79dd7a425bce3252d526de885b1c8826451e089c, tests exit 1 with exactly 1 failed | 11 passed (12) -- 'renders a refusal message byte-identically to a screen description', caught at the `{$User.Id}` probe ('expected by usr_7, received by '). The second engine passed the obvious {record.name} probe, which is why the probe set is six templates and not one; restored byte-identical. GATES, mechanically derived and reconciled: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` over 9 changed paths vs merge base 3aaea3879 derived 65 commands; `--ran` with every exit code recorded reports 'Run reconciliation -- 65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN' with 'EXIT CODES -- all 65 accounted families carry one, so the NOT-MEASURED count above is DERIVED from them'. DENOMINATOR: 65 derived / 65 run / 0 NOT MEASURED / 0 UNRUN. Three needed a prerequisite the first pass lacked and were re-run rather than recorded as failures: check:dual-build-cjs-loads and check:i18n exited 3 PREREQUISITE NOT MET ('nothing was measured') until `turbo run build --filter=./packages/* --filter=./packages/*/*`, then exit 0; check:type-check-debt exited 3 twice, once for the same unbuilt closure and once on an OOM at the --max-old-space-size=4096 this container prefixes onto heavy commands (the gate prints that ceiling itself), then exit 0 without the tightened cap -- '76/80 workspace packages type-checked, 5 ledger entries re-measured, 55 raw tsc errors, none above its recorded number'. Clause-2 carrier predicate `node scripts/pm/check-clause2-carriers.mjs --pair 18109` EXIT 0: 'the clause-2 declaration is readable in the fixed spelling and both carriers agree'. Merge hygiene: origin/main merged at 3aaea3879 via scripts/pm/os-regen-merge.sh, which left no generated artifact to regenerate and no os-regen-pending deferral; `pnpm install --frozen-lockfile` re-run afterwards per the stale-artefact rule.", "clause_2": "yes -- re-derived from the DELIVERED diff against the built output, not inherited from the claim. The package publishes files [dist, README.md, CHANGELOG.md] with one entry ./dist/index.d.ts. Carriers: (1) `RunRecord` gains `refusalMessage?: string` at dist/index.d.ts:743 and `type RunRecord` is in the entry's export list -- a NEW KEY on an already-published payload, the mandatory yes; (2) `TerminalRunStatus` widens from 4 members to 5 at dist/index.d.ts:694-696, exported, with the runtime value shipping too (dist/index.js: TERMINAL_RUN_STATUSES = [completed, failed, cancelled, timed_out, refused]); (3) `SysAutomationRun`, exported as a value, gains the `refusal_message` field and the `refused` option at dist/index.d.ts:9339. The terminal status is NOT what carries it -- `refused` was already a declared ExecutionStatus member, so `status` is an existing key taking a newly-legal value, exactly as the claim warned. DISCRIMINATING CONTROLS in the same probe: `interpolateText` (new in this diff, internal to builtin/) = 0 hits in the published d.ts, and `isTerminalRunStatus` (exported from engine.ts, not re-exported from the barrel) = 0 hits -- so the probe is not reporting every identifier in the diff as published; positive control `RunRecord` = 18 hits (published before this diff), negative control `zzzNotASymbol` = 0. Changeset graded `minor`, never patch, as the ruling grades this lane. `needs:contract-review` applied to PR #18109 by additive REST POST and confirmed by comparative read-back (union of write set and target minus read-back = none stripped; a concurrent labeler added documentation/tests/tooling, not mine to touch).", "mcp_calls": "0 -- no MCP GitHub tool was called for the whole run; every GitHub read and write went through the repo-scoped REST proxy with curl, after a repo-scoped read probe returned HTTP 200.", "api_writes": "3 REST writes + 4 git pushes. REST: POST /repos/objectstack-ai/objectstack/pulls (draft, HTTP 201, PR #18109); POST /repos/objectstack-ai/objectstack/issues/18109/labels (HTTP 200, needs:contract-review); POST /repos/objectstack-ai/objectstack/issues/15788/comments (this report). git push origin claude/issue-15788-flow-end-refused-outcome x4 (empty branch as the write-route probe, then after each of the three commits plus the merge). No PATCH of any body, no POST /issues, no MCP write.", "open_questions": [ { "question": "A refusing `end` INSIDE a structured region (a loop body, a try/catch region): the #14945 ruling says nothing about regions. This PR makes it a loud failure at the same line where runRegion already refuses a durable pause. Should it instead PROPAGATE out of the region and terminate the run?", "options": [ "A. Keep the loud refusal (what shipped). A refusal terminates the RUN and a region body cannot end one; left to propagate, the signal unwinds into try_catch's own catch arm, which reads every throw as the try region FAILING -- so the author's refusal would run the error path and the run would still record `completed`, i.e. the old silence with an extra step. Nothing an author had is narrowed: an `end` in a region was a no-op whatever its outcome, so this shape has never once been honoured.", "B. Propagate the signal out of every container so a refusal inside a loop or try region terminates the whole run. Needs try_catch (and a check of parallel/map) taught to rethrow the refusal rather than catch it, plus a decision about the in-flight siblings of a parallel gateway -- the same scope ADR-0019 M1 explicitly left out for durable pause." ], "recommendation": "A for now, which is what shipped -- it is the interim that 'prefer failing to falling back' decides and it forecloses a silent wrong answer; B is a real design question for the ruling seat, and if it is chosen the loud message is the discoverable place a reader lands." } ], "out_of_scope_findings": [ "to file (candidate class c, metadata whose declared effect is silently not delivered where the author means it) -- a `subflow` CHILD that refuses is rolled up by its parent as an ordinary success: builtin/subflow-node.ts branches only on `child.status === 'paused'`, so a child run recorded `refused` returns success:true and the parent walks straight down the subflow node's out-edges. Not a regression this PR introduces (the parent behaved identically when a refusing child merely completed) and not ruled on by #14945, but it becomes reachable the moment authors write refusals. Dedupe words: subflow child refused rollup, subflow-node.ts child.status paused, refused subflow parent continues, end node outcome refused subflow, #14945 lane 2 subflow.", "noted, not filed: packages/services/service-automation/src/plugin.ts describes the sys_automation_run retention scope as `{ status: { $in: ['completed', 'failed'] } }` in two comments (:138 and :787) while the object has declared four members since #15223 and five as of this PR. Pre-existing staleness, not made false by this change, left alone to keep the diff at the vocabulary it actually widens. Carrier: the next PR to touch sys_automation_run's retention.", "noted, not filed: the naming hazard the triage seat measured (comment 5550511372) is real and is answered in code rather than restated -- `refused` in this package overwhelmingly means a GUARD refusal (the engine declining to execute, a kind of failure) while this lane's `refused` means a successful evaluation that said no. Disambiguation docblocks are written at both definition sites (FlowRefusalSignal in engine.ts, and the pin file's header) so a future grep reads the sense at the site rather than from the word. Carrier: anyone grepping `refused` in service-automation.", "noted, not filed: `pnpm check:type-check-debt` OOMs under the container's standard `NODE_OPTIONS=--max-old-space-size=4096` prefix and reports it as PREREQUISITE NOT MET rather than a finding, which is correct but costs a full re-run; the gate's own output names the ceiling ('the caller's NODE_OPTIONS, which is tighter'). Not a defect in the gate and not this card's business. Carrier: the next seat that prefixes the heap cap onto a full gate sweep." ] }Generated by Claude Code
claude commentedon Sep 14, 2026
Landed. PR #18109 merged
2026-09-14T04:39:45Zascca699149d8cbbd446813fe7dbda1ac7266095da, enqueued03:59:44Z(added_to_merge_queue). Lane 2 of the #14945 ruling 2′ is complete.Verified on
origin/mainwith controls, ⛔ not inferred from the PR closing:Clause-② review of record
Clause-②: yes. The at-tier record is comment 5658765796 on PR #18109, judging head60ca35a2f2ea33de5b256900f57f37471a4a8643, verdict PASS, carryingServed-tier: 1/1 CONTRACT_REVIEW_TIER— the constant's NAME, ⛔ not a model identifier (the spelling that landed in66e34d14d).Implemented-by:names the dev branch andReviewed-by:a different session, so the independence pair holds. The dual carrier was cleared from this card and the PR in one stroke afterwards;--pair 18109read 0 before and 0 after the strip, and the governed-surface predicate read 0 of 9.⭐ The review corrected a real error in the PR's own evidence, worth carrying forward. The body claimed the runtime value of
TERMINAL_RUN_STATUSES"ships too". Measured on a rebuilt tree: the array is bundled insidedist/index.js, but it is not re-exported fromsrc/index.ts, is absent from the.d.tsexport list, and'TERMINAL_RUN_STATUSES' in await import(entry)readsfalse.yesstands onRunRecord.refusalMessage?(a new key on an already-published payload — the mandatory limb) and on the exported typeTerminalRunStatuswidening 4→5. Verdict andminorgrade unchanged.What shipped
The
endnode honoursoutcome: 'refused': aFlowRefusalSignal(twin of the existingFlowSuspendSignal) answered by one sharedfinishRefusedRunat all three terminal producers —execute(),resumeInternal,executeWithoutRetry— because this file lostsuccessMessage(#9414) and the durable pause (#9510) by implementing at one exit only. Envelope issuccess: true, status: 'refused', refusalMessage, with noerror,errorMessage,successMessageorrunId; the retry ladder stops on it and spends no budget. The message renders through the same interpolation a screendescriptionuses, ⛔ not a second template engine — pinned with six interpolator-specific templates, and the reverse-verification swapped in a plausible second engine to prove that pin CAN fail.packages/specuntouched: lane 1's contract was complete.Follow-ups already filed, so nothing is lost
subflowchild that endsrefusedis rolled up by the parent as an ordinary success — the refusal reaches nobody #18110 — asubflowchild that endsrefusedis rolled up by the parent as an ordinary success. Deduped against service-automation:subflowandmaproll a child's contained failures into the parent'sfailedthrough theExecutionStepMetricsfailure slot ruled on #15617 #16314, which is the inverse direction (child failures rolling INTO the parent'sfailed); ⛔ do not close either as a duplicate of the other.endINSIDE a structured region propagate out and terminate the run, or stay a loud refusal at the region boundary? #18112 — Decision: should a refusingendinside a structured region propagate out and terminate the run, or stay a loud refusal at the region boundary? Option A shipped as the interim; B needstry_catchtaught to rethrow plus the parallel-sibling scope ADR-0019 M1 left open.Both ungraded — grading is triage's.
ⓘ Noted for whoever next touches this file, not filed (this seat is going off shift and is opening no new work):
engine.ts:4482still reads 「All four {@link TERMINAL_RUN_STATUSES} members」 — there are now five. Pre-existing-style staleness of the same class the implementer flagged atplugin.ts:138/:787; a comment only, nothing behavioural.Generated by Claude Code
fix(approvals): classify every ExecutionStatus member for the dead-ru…
fix(pm): H19 judges a `Blocked-by:` block against the body's target s…
feat(service-automation): the flow `end` node honours `outcome: 'refu…
test(service-automation): re-home #15788's region `end` case to the p…
feat(app-shell): render a flow that ended with `outcome: 'refused'` a…
fix(service-automation): on the synchronous path, a child run that re…
feat(spec)!: a structured region body refuses a pause-capable node an…
fix(service-automation): a durable suspension inside a structured reg…
fix(service-automation): a resumed child's refusal rolls up on both l…