Skip to content

[finding] service-automation: a child that PAUSES and then refuses on its resumed leg still rolls up fail-open — #18110/#18555 fix the synchronous leg only, and the up-bubble path strands the parent paused forever #18714

Closed

Description

@huangyiirene

Surfaced by the contract-review-tier review of PR #18706 (the #18110 + #18555 delivery) and filed by the domain:services seat, session_01QGMBhvUoyD8t5zY8xHQhnP. ⛔ Unlabelled for domain:* and ungraded on purpose — routing and grading are the triage seat's.

⚠️ Neither leg below is a regression from #18110/#18555. Both pre-date that change, which deliberately covers only the synchronous engine.execute return in the two executors, per its governing ruling. This card exists so the remaining half is tracked rather than implied-fixed by the release note.

Measured, ⛔ not read off the code

Driven with a scratch probe (subflow → a pausing node → a refusing end) by the reviewer at PR head af0edd84f:

leg measured result
Delegated resume — engine.resume(parentRunId) parent returns { success: true, successMessage: … }, parent run row records completed, and the downstream node ran. ⇒ the identical fail-open shape #18110 fixes, still open here.
Up-bubble — engine.resume(childRunId) child row records refused, but the parent stays paused and remains in listSuspendedRuns() indefinitely.

Call sites behind it:

  • packages/services/service-automation/src/engine.ts:6266–6350 — the delegated-resume branch tests only paused / !success, so a refused child falls through the ordinary success exit (exactly the branch-set shape the two fixed executors had).
  • engine.ts:6676 — bubbleToParent is called only on the completion path; the refusal arm at :6696 returns without bubbling, which is why the parent is never resolved either way.

⭐ Why this is worth a card rather than a note

engine.ts:6689–6693 describes the resumed leg, in its own words, as 「the one a screen flow actually takes」 — so the uncovered leg is the common one for the feature that made refused reachable at all (#15788). The two halves also fail differently: the delegated leg loses the refusal silently (fail-open), while the up-bubble leg strands a run in paused (a leak, visible in listSuspendedRuns()).

⛔ What this card does NOT claim

Dedupe words

resumed leg refused rollup · bubbleToParent refusal arm no bubble · engine.resume parentRunId refused child · parent stays paused listSuspendedRuns · 18110 synchronous leg only

Filed at 2026-09-17T16:23Z. ⛔ No domain:*, no priority, no pm-state — deliberately bare for the triage sweep's disjunction ①. Evidence provenance: the at-tier (claude-fable-5-1, confirmed from the reviewer's transcript) clause-② review of PR #18706, advisory A2.


Generated by Claude Code

Activity

  1. huangyiirene commented on Sep 18, 2026

    @huangyiirene
    CollaboratorAuthor

    Deferred this round — serial behind #18881, and the fold-or-serial question answered explicitly

    domain:services seat (#6021) · session_019hBqDVrwbijUCoK9qsss2E · R1 · written 2026-09-18T21:47Z. ⛔ Report-only: no label written, no claim taken, this card keeps pm:queue and stays dispatchable. Board readings taken at this fire.

    Why this is written now rather than when the card is picked up

    This card and #18881 are both p1, both pm:queue, and both land in packages/services/service-automation/src/engine.ts. That is a shared hot file between two queued cards, so the protocol requires this seat to answer fold or serial out loud rather than let a default happen. #18881 was dispatched this fire; this card was not. The reasoning is recorded here so the next seat inherits a decision, ⛔ not a silence.

    Fold-or-serial — SERIAL, and two of the five gates fail

    gate verdict
    ① same defect shape, same repair ⛔ FAILS. #18881 is a region-containment defect: a node inside loop / parallel / try_catch durably suspends and the run reports success with nothing run. This card is a resume-leg rollup defect: a child that pauses and then refuses is either lost fail-open (delegated resume) or strands its parent in paused (up-bubble). Same file, same subsystem — ⛔ not the same defect and not the same repair. Same keyword is ⛔ not gate ①.
    ② same package / region ✅ passes — both service-automation, both engine.ts.
    ③ every member already ruled or graded ⛔ FAILS for this card. #18881 carries a maintainer ruling (batch #153 item 1, ruling D on #15646). This card is graded p1/Bug by triage but the card itself says verbatim 「⛔ It does not prescribe the fix」 and names the mechanism choice as 「a design question that touches the engine's resume machinery」.
    ④ each member independently checkable ✅ passes — the card names two legs, each with its own measured signature.
    ⑤ exclusion list named ✅ this comment is it.

    ⇒ Two independent gate failures. ⛔ Not foldable. They run serial, #18881 first.

    🔴 The re-pricing question the next dispatch must answer BEFORE this card goes out

    ⛔ Do not dispatch this card on the estimate that stands today. When #18881's PR lands, its dev's report must be read against this card with the mandatory question:

    Does #18881's change make this card simpler, harder, unnecessary, or leave it unaffected?

    This is not a formality here — #18881 adds a named structured failure when a contained node durably suspends. If that failure fires on the shape this card's probe drives, part of this card may already be closed by it. ⭐ The seat's own guess is that it does NOT — this card's probe is subflow → pausing node → refusing end, which need not sit inside a structured region at all — but that is a guess, ⛔ not a reading, and it is recorded here as a falsifiable assumption rather than a premise.

    Readings this seat took at 2026-09-18T21:40Z that this card should not have to re-take


    Generated by Claude Code

  2. self-assigned this
    on Sep 19, 2026
  3. huangyiirene commented on Sep 19, 2026

    @huangyiirene
    CollaboratorAuthor

    Claim: PM loop round 1
    Session: session_019hBqDVrwbijUCoK9qsss2E
    Branch: claude/issue-18714-resumed-leg-refusal-rollup
    Worktree: objectstack-issue-18714
    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's dispatch-gates.mjs --repo objectstack-ai/objectstack --tier packages/services/service-automation/src/engine.ts returned verbatim 「no path-derived mandate: the surface hits none of the 3 declared glob(s) … floor sonnet · default opus · ceiling fable」. Kept at default: the card names two legs and ⛔ does not prescribe the fix, so the mechanism is a live design judgment.
    Clause-②: no
    Thread-read: 5736595238
    Serial constraints cleared: the hot file is engine.ts. Its previous holder #18881 MERGED as PR #19140 (verified on origin/main with controls), which is what unblocks this card. Lane pm:dispatched now = #18336 (this seat's, surface core/security + plugin-auth + plugin-security + organizations test ⇒ disjoint from service-automation) and #16506 (⛔ another seat's, on src/email-service.queue-delivery.test.ts ⇒ disjoint). ⛔ No sibling card pins behaviour this one asserts.


    🔴 Re-pricing against the card that was blocking it — the mandatory question, answered from measurement

    Does #18881's change make this card simpler, harder, unnecessary, or leave it unaffected?

    ⭐ Unaffected. ⛔ Not a guess this time — when this card was deferred, this seat wrote that its probe 「need not sit inside a structured region at all」 and explicitly marked that a guess, not a reading. It is now measured on origin/main after #19140 landed:

    probe reading
    isRegionSuspensionRefusal call sites in engine.ts 3 — the import (:214), the one-refusal-one-failure suppression (:9528), and a re-throw (:10111)
    …of those, any on the resume or up-bubble path 0
    bubbleToParent sites 9 — still called on the completion path (:6689), defined at :7067; the refusal arm's structure is unchanged
    negative control (bogus token) 0

    ⇒ #18881 fires inside runRegion / executeNode. Neither of this card's two legs is touched, so its cost, its shape and its two-pin acceptance all stand as filed. ⛔ The estimate is ⛔ not carried over from filing time — it is re-taken here.

    ⚠️ One NEW adjacency #19140 created — a question for the dev, ⛔ not a finding and ⛔ not a fence

    engine.ts:10111 now re-throws on isRegionSuspensionRefusal. A run that resumes into a structured region can now meet that error type on a path this card is rewriting. ⇒ check whether the resume legs need to handle it, and say what you found. This seat did ⛔ not establish that they do.

    Standing constraints carried from the card and its triage

    Written by this seat at 2026-09-19T00:40Z. Seat post: #6021.


    Generated by Claude Code

  4. added a commit that references this issue on Sep 19, 2026
  5. huangyiirene commented on Sep 19, 2026

    @huangyiirene
    CollaboratorAuthor

    os-dev-report
    {
    "issue": 18714,
    "status": "done",
    "branch": "claude/issue-18714-resumed-leg-refusal-rollup",
    "pr": "#19158",
    "session": "session_019hBqDVrwbijUCoK9qsss2E",
    "premise_still_valid": true,
    "summary": "Both resumed legs of the subflow chain now roll a child run's REFUSAL up to its parent. The delegated-resume block (engine.resume(parentRunId)) records the refusal when the child it drives answers status 'refused'; bubbleToParent gains a refusal argument so a child resumed directly (engine.resume(childRunId)) also resolves its parent instead of leaving it parked. Both arms hand the refusal to ONE throw site inside the resume's traversal try-block, past claimAdvance/forgetSuspendedRun and before the traversal, which raises the existing internal FlowRefusalSignal so the run terminates through the single finishRefusedRun chokepoint: parent row reads 'refused', carries the child's rendered refusalMessage, fires no successMessage, walks no out-edge, and keeps the child's #4354 rollup. Multi-level chains resolve by the same induction completions already use. ISSUE BODY: no sanitizer truncation — the body ends on its intact dedupe-words section and footer, and both comments plus the PM claim read whole. CLAIM VERIFIED: newest Claim: comment 5737929003 names this branch. ASSIGNEE: set by the PM to huangyiirene before dispatch; this run never wrote it.",
    "tests": "ABLATION (two legs, one at a time, via scripts/ablation-replace.mjs — anchor-must-hit, on-disk blob proof, proven restore). The pins resolve ./engine.js from package SOURCE (in-package relative import, no dist on the resolution path), which the reddening itself demonstrates. LEG 1, delete the delegated-leg detection: tool printed 'anchor 1 to 0, blob 90ccd6c29edf to fe1d7763fc67'; result 4 failed | 7 passed; the four reds are all leg-1: "expected undefined to be 'refused'" (twice), "expected undefined to be 'Refused: vip is not eligible'", and "expected [ 'child-work', 'downstream' ] to deeply equal [ 'child-work' ]"; every leg-2 assertion and both controls GREEN. LEG 2, delete the up-bubble of the refusal: 'anchor 1 to 0, blob 90ccd6c29edf to 6098b376281a'; result 3 failed | 8 passed; the three reds are all leg-2: "expected 'paused' to be 'refused'" (twice, on the parent's run row and on its summary) and "expected true to be false" (hasSuspendedRun on the parent); every leg-1 assertion and both controls GREEN. Two DISTINCT failure signatures — wrong-answer fail-open versus leaked-run — not one measurement restated. Both restore legs printed 'ok restored: blob == HEAD (90ccd6c29edf) and git diff HEAD is empty', and git status --porcelain was empty after each. TWO leg-2 assertions are measured green on BOTH sides and are annotated in the file as such, so no reader mistakes them for pins: the downstream-did-not-run one, kept as the pin on the WRONG fix of bubbling the refusal as a completion; and the child-envelope invariance one. SUITES: pnpm --filter @objectstack/service-automation test printed 'Test Files 140 passed (140)' and 'Tests 1671 passed (1671)', exit 0 — so the synchronous leg (#18110 and #18555), the region refusal (#18881) and the retryable delegated resume-bag codes (#14379) keep their pins. pnpm --filter @objectstack/service-automation typecheck exit 0 including check:test-typecheck ('0 file(s) / 0 error(s)'). New pin file alone: 'Test Files 1 passed (1)' and 'Tests 11 passed (11)'. All heavy runs went through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-18714; every verdict read off its printed 'VERDICT command-exit' line, never a bare exit variable after a pipe.",
    "gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (NO paths passed; change set derived from merge base 6819dcb) yielded 59 commands. ALL 59 RUN, exit codes landed to disk, reconciled with --ran: 'dispatch-gates --ran: 59 derived famil(ies) accounted for — 59 run, 0 NOT-MEASURED (a DERIVED zero — all 59 recorded an exit code and none of them is 3)', exit 0. Two gates first answered exit 3 PREREQUISITE NOT MET (pnpm check:dual-build-cjs-loads and pnpm check:type-check-debt — both demand a built closure); after 'pnpm exec turbo run build --filter=./packages/* --filter=./packages// --concurrency=2' (72/72 tasks successful, exit 0) both were re-run and answered exit 0 ('104 published require entry point(s) across 67 package(s) load' and '4 ledger entr(ies) re-measured, 53 raw tsc error(s) total, none above its recorded number'). REPO-LEVEL SCAN, run whole and NOT narrowed: pnpm lint (eslint . --no-inline-config) exit 0 on the final commit b2203ba. pnpm check:nul-bytes green, plus a hand grep -naP over the three changed files for control bytes: zero hits. CLAUSE-2: node scripts/pm/check-clause2-carriers.mjs --pair 19158 exit 0, verdict 'the clause-② declaration is readable in the fixed spelling and both carriers agree, and its diff carries no widening tell' — card claim and PR body both DECLARED 'no'. NO needs:contract-review is owed on this PR and none is present on it. REMOTE CI on PR head b2203ba: 34 check runs completed, 31 success / 3 skipped / 0 failure — all SEVEN required contexts GREEN (Lint & Repo Gates, TypeScript Type Check, Test Core, Dogfood Regression Gate, Build Core, Temporal Conformance (live PG + MySQL), Governed Surface Queue Guard). Not a substitute for CI's own account: the 51 artifact-roster families, 11 declared-wide families, 5 path-scheduled CI jobs and the always-runs tail sit outside the derived 59, as the tool states.",
    "line_budget": "NOT APPLICABLE — the diff touches no skills/** package and no published SKILL.md, so no line ratchet is owed and none was paid. Diff size for the record: 3 files, +525/-4 versus origin/main — engine.ts +184/-4 (two detection arms, one shared throw site, one module-private interface, docblocks), resumed-leg-refusal-rollup.test.ts +318 (new), .changeset/18714-resumed-leg-refusal-rollup.md +27 (new). The auto-labeler put size/l on the PR; that is another actor's write and was left alone.",
    "deviations": "NONE that narrow verification: the repo-level lint ran whole rather than narrowed, and all 59 derived gate families ran with zero NOT-MEASURED. Two deliberate, declared choices worth the seat's eye. (1) The two resumed legs share ONE throw site and one terminal chokepoint, so a mutation AT THE SHARED SITE would redden both pins. The legs are kept independently dissolvable by their own detection arms, which is exactly what the two ablations above mutate; the two-pin acceptance should be read against those, not against the shared site. (2) The changeset is a patch, not a minor: nothing published moves (ChildRunRefusal is module-private, resumeInternal and bubbleToParent are private), which is also why Clause-2 is 'no'. No label was written from here, per the dispatch's write budget. No PR body PATCH was issued — the body was written once, at creation, and read back byte-identical. FILE SURFACE HELD: packages/services/service-automation/src/ plus .changeset/ and nothing else; zero packages/spec.",
    "files_changed": [
    "packages/services/service-automation/src/engine.ts",
    "packages/services/service-automation/src/resumed-leg-refusal-rollup.test.ts",
    ".changeset/18714-resumed-leg-refusal-rollup.md"
    ],
    "zone2_findings": "ASSUMPTION 1 (the blocker #18881 / PR #19140 does not change this card) — CONFIRMED by re-measurement, not carried over. On origin/main isRegionSuspensionRefusal has exactly 3 sites in engine.ts (import :214, the one-refusal-one-failure suppression :9528, the inner-boundary rethrow :10111); on this branch the same 3, shifted to :214 / :9704 / :10287 by my insertions. NONE is on the resume or up-bubble path. bubbleToParent was called on the completion path only (:6689 on main); my change adds exactly one more call site and changes nothing about the existing one. Negative control: a bogus token grep returned 0 hits, exit 1. ASSUMPTION 2 (the NEW :10111 rethrow adjacency — a question, not a fence) — ANSWERED: the resume legs do NOT need to handle it. The two predicates are structurally disjoint. FlowRegionSuspensionRefusalError is an Error subclass branded with a registered Symbol.for and carries no __flowRefused; FlowRefusalSignal is a non-Error sentinel carrying __flowRefused and no symbol. Neither isRefusalSignal nor isRegionSuspensionRefusal can answer true for the other's value. A run that resumes INTO a structured region and meets the region refusal still falls to resumeInternal's generic failure arm and is failed, which is #18881's intended outcome; my arms sit above that and never intercept it. Held by the full package suite staying green, region-durable-suspension-refusal and nested-region-parity included. ASSUMPTION 3 (the card's line numbers have drifted) — CONFIRMED, and re-located by behaviour rather than by number: the delegated-resume branch the card cited at :6266-6350 is the correlation.startsWith('subflow:') block, and the bubble arm it cited at :6676 / :6696 is the completion-path bubbleToParent call and the isRefusalSignal catch arm beside it. ASSUMPTION 4 (whether to reuse PR #18706's FlowRefusalSignal channel) — MEASURED and REUSED, and it is the right channel: both existing producers of a refusal already unwind through it to finishRefusedRun, the resume path already carries an isRefusalSignal catch arm, and reusing it means the resumed legs mint no second unwinding protocol, no second terminal exit and no new vocabulary.",
    "issue_18112_reading": "READ IN FULL — the card plus its single ruling comment (hotlong, 2026-09-17, director batch #146 item 2, maintainer 「146 同意」). What it CLOSED: letter A — a refusing end inside a structured region stays a loud failure at the region boundary, plus the authoring-time refusal folded into #15646. What it deliberately left CLOSED: option B — teaching try_catch (and parallel / map) to RETHROW the refusal instead of catching it, and the paired decision about the in-flight siblings of a parallel gateway; B 'reopens only with #3267's 「支持」, for both signals together'. MY MECHANISM STAYS OUTSIDE IT on all three counts: (a) it teaches no container to rethrow — runRegion, try_catch, parallel, loop and the map executor are untouched by this diff; (b) it decides nothing about parallel siblings; (c) it adds no engine surface. It also cannot reach that territory by construction: a region body runs synchronously inside the enclosing run and cannot carry a durable pause at all (#3267 禁, enforced at authoring by #18688 and at runtime by #18881), so the shape this card fixes — a run PARKED at a subflow or map node awaiting a child — can never be inside a region body. The two arms are on the top-level durable-resume machinery only.",
    "mcp_calls": "0 — no MCP GitHub tool was called, read or write. Every GitHub read and write went through the REST proxy with curl and GITHUB_TOKEN.",
    "api_writes": "2 — POST /repos/objectstack-ai/objectstack/pulls (draft PR #19158) and POST /repos//issues/18714/comments (this report). No label write, no PATCH, no POST /issues. The three git pushes to the feature branch are git, not REST writes.",
    "open_questions": [],
    "out_of_scope_findings": [
    "noted, not filed: the delegated-resume block special-cases the 'subflow:' correlation only, so a map parent reaches its child's outcome exclusively through the up-bubble. Consistent, covered by leg 2, and a shape of the two node types rather than a defect — none of the three filing classes. Successor: none known.",
    "noted, not filed: bubbleToParent's per-outcome #4632 grading (the stranded error arm versus the functional warn arm) is unreached on a refusal, because a refused parent answers success true. That is correct — a refusal is not a degradation — but it means the strand-recording arm stays exercised only from the completion side, by the existing subflow-bubble-strand-log-level pin. Observation, not a defect. Successor: none known.",
    "noted, not filed: the docs-drift bot named two RELEASE-OWNED pages (content/docs/releases/v16.mdx and v17/17-0.mdx) as naming AutomationEngine. Read-only by AGENTS.md; nothing in them is falsified by this change (no rename, no removal, no advertised behaviour withdrawn), and no edit was made. Successor: the release seat, at release time."
    ]
    }


    Generated by Claude Code

  6. huangyiirene commented on Sep 19, 2026

    @huangyiirene
    CollaboratorAuthor

    ACCEPT — PR #19158, reviewed against GitHub and the tree, ⛔ not against the report

    domain:services seat (#6021) · session_019hBqDVrwbijUCoK9qsss2E · R1 · written 2026-09-19T01:30Z. Reviewed head b2203ba3b7.

    Checklist — each line a reading this seat took

    item verdict
    PR shape draft ✓ · base main ✓ · first line Fixes #18714 ✓ · Clause-②: no line-initial ✓
    path surface (/pulls/19158/files) 3 files, all under packages/services/service-automation/src/ + .changeset/ — ⛔ nothing outside; ⛔ zero packages/spec
    clause-② declaration leg ⭐ verified independently, ⛔ not accepted on the declaration: src/index.ts is not in the diff, and ChildRunRefusal / resumeInternal / bubbleToParent each resolve 0 times in the package entry. Control on the same instrument: that entry carries 26 export lines ⇒ the zeros are readings. Nothing published moves.
    --pair 19158 exit 0 — both carriers agree on no, no widening tell
    CI (latest run per name) 31 success · 3 skipped · 0 red · 0 running; skips are the familiar conditional set (Build Docs, Console Pin Gate, Packed-tarball smoke (opt-in))
    report location os-dev-report on the issue (5738255926) ✓ · mcp_calls 0 · api_writes 2, exactly the budget ✓

    🔴 The acceptance that mattered: two legs, two pins — triage's non-negotiable

    Triage's instruction was verbatim 「另起一张 PR,并让两条腿各有一条能变红的钉」. Checked structurally, ⛔ not on the report's word: the suite is 11 tests laid out as defect leg 1 / control leg 1 / defect leg 2 / control leg 2 — each leg has its own defect block and its own control.

    The two ablations produce genuinely different signatures, which is what makes them two measurements rather than one restated:

    ablation signature
    delete the delegated-leg detection expected undefined to be 'refused' — the refusal is lost, fail-open
    delete the up-bubble of the refusal expected 'paused' to be 'refused' + hasSuspendedRun true — the parent is leaked

    ⇒ wrong-answer versus leaked-run. Exactly the two failure modes the card describes, and each ablation left the other leg's assertions and both controls green.

    ⭐ The disclosure that earns the most trust here is the one against the deliverer's own interest: the dev declared that the two legs share one throw site, so a mutation there would redden both pins — and that the two-pin property therefore has to be read against the detection arms, which is what the ablations mutate. ⛔ It did not let the shared chokepoint pass as though the pins were independent everywhere. It also annotated two assertions as green-on-both-sides so no later reader mistakes them for pins.

    Zone-2 assumptions — both answered, one by falsifying the question

    • Assumption 1 (the blocker doesn't change this card): confirmed by re-measurement, ⛔ not carried over — isRegionSuspensionRefusal still has 3 sites, none on the resume or up-bubble path.
    • Assumption 2 (the new :10111 re-throw adjacency — asked as a question, ⛔ not a fence): answered, and the answer is no. The two predicates are structurally disjoint — FlowRegionSuspensionRefusalError is an Error subclass branded with a registered Symbol.for and carries no __flowRefused; FlowRefusalSignal is a non-Error sentinel carrying __flowRefused and no symbol. Neither detector can answer true for the other's value. ⇒ ⭐ the right outcome for a question dispatched as a question: it was investigated and closed with a mechanism, ⛔ not deferred and ⛔ not turned into speculative work.
    • [Decision] service-automation: should a refusing end INSIDE a structured region propagate out and terminate the run, or stay a loud refusal at the region boundary? #18112 read in full and the mechanism stays outside it on three counts — no container taught to rethrow, nothing decided about parallel siblings, no engine surface added. Plus a structural argument: a region body runs synchronously and cannot carry a durable pause at all, so the parked-at-a-subflow shape this card fixes can never be inside one.

    Changeset

    patch, not minor — nothing published moves, which is the same fact that makes Clause-②: no honest. Consistent.

    Out-of-scope findings — placed

    Three noted, not filed, each with its successor named, and this seat agrees none meets the three filing classes: the subflow:-only correlation special-case (a shape of the two node types, covered by leg 2) · bubbleToParent's per-outcome grading being unreached on a refusal (correct — a refusal is not a degradation) · the docs-drift bot naming two release-owned pages, correctly left alone since nothing in them is falsified and content/docs/releases/** is ⛔ never edited in a code PR.

    ⇒ ready + enqueued.


    Generated by Claude Code

  7. removed their assignment
    on Sep 19, 2026
  8. huangyiirene commented on Sep 19, 2026

    @huangyiirene
    CollaboratorAuthor

    ✅ Landed — PR #19158 MERGED, verified on origin/main, half-state cleared

    domain:services seat (#6021) · session_019hBqDVrwbijUCoK9qsss2E · R1 · written 2026-09-19T02:02Z.

    probe value
    (#19158) in the commit log 1
    positive control (#19136) 1
    negative control (#99999) 0
    the new two-leg pin file on main 1
    ChildRunRefusal sites in engine.ts 6
    ⭐ Clause-②: no still holds AFTER landing — the refusal type in the package entry 0 (control: entry carries 26 export lines)
    negative control on a bogus symbol 0

    ⇒ the delivery is on main, and the clause-② declaration is true of the landed code, ⛔ not merely of the declaration.

    Half-state cleared

    Closed completed still carrying pm:dispatched + assignee. Now priority:p1, domain:services, 0 assignees. ⛔ Cleared with this note, ⛔ not silently.

    ⚠️ Fourth card in a row to need this. A closing keyword closes the issue and leaves the board exactly as it was — this is now written into the seat post's standing facts so the next seat does not re-learn it four times.

    What landed

    A child flow that durably pauses and only then refuses now rolls that refusal up to its parent on both resumed legs — the delegated resume (which used to lose it fail-open, reporting completed and walking the out-edge) and the up-bubble (which used to strand the parent paused in listSuspendedRuns() forever). Both arms hand the refusal to one throw site and terminate through the existing finishRefusedRun chokepoint, so no second unwinding protocol and no new vocabulary were minted.

    ⭐ Triage's non-negotiable — 「两条腿各有一条能变红的钉」 — was met and measured: the two ablations produce different signatures (expected undefined to be 'refused' = lost; expected 'paused' to be 'refused' + a live hasSuspendedRun = leaked), each leaving the other leg green.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions