feat(engine): containers through a cancel, a retry and a restart — a failed view fails its running containers, so the run settles (Phase 3 slice 15c) #118
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/container-recovery"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Phase 3's slice 15c. A run with a running container now always settles.
Cancel, retry and a restart already worked with expanded rows. They go through slice 14's ledger transitions and 15b's container step, so this slice pins them with tests rather than changing them. Two ways a container could keep its run live for good are now closed:
Both follow ADR 0016 and its Phase 3 decisions: a run that has given up must settle, and so must a run whose view has failed.
What changes
A failed view fails its containers (P4).
:and the container's id. A run that holds an expansion started under V18, so its node ids contain no:or/.A container row the graph can't step fails (P17).
Pinned, with no code change:
Tsumugi. A fresh engine over the EF ledger resumes a container that a stopped process left part-way through its iterations. It reads the stored expansion back, adds the next iteration's rows after the run's existing rows, and collects.
Docs.
Not in this slice
concurrent-settlesflake inEfRunLedgerTestshas its own investigation notes. It ran green 40 times alone under CPU load, and six full suite runs with a leak probe attached were green too. One lead: EF Core's automatic savepoint inside the ledger's write transaction, which was seen cancelled once. That fix comes after this slice.mapandlooptypes (17, 18).Verification
.NET tests. All six projects pass under TZ=UTC and in the local zone:
The tests cover:
mm, whose id sharesm's prefix, and at the top level), and a run that had already given up. It checks the order, the message, the work counted, the events and the run's status.Coverage of the changed .NET lines: 31/31 lines, 24/24 branches.
No browser pass: nothing a writer sees changes. Tsumugi registers no container type.
Mutation probes:
Reviews. The counts come from the saved review JSON.
:;🤖 Generated with Claude Code
A run with a running container now always settles. Cancel, retry and a restart already worked with expanded rows through slice 14's transitions and 15b's step; this slice pins them, and closes the two ways a container could keep its run live for good. - P4: a view that no longer runs can step no container. Each running container fails with the reason the graph no longer runs once nothing in its body runs or waits for input, the innermost first, each counted as the pass's work — what was in flight settles on its own, and a failed view starts nothing new; a row waiting beside a container holds the run live, not the container, as a quiet body does in the step. - P17: a running row with an expansion whose node the snapshot lacks ("The run's graph has no node x."), or whose type plans no body ("Node type 't' plans no body."), fails instead of staying live, as an orphan whose node the snapshot does not have does (P11). - Pinned: a cancel marks the container and its body's rows and writes nothing more for it (P12); a retry returns a failed container to running with its expansion, so only its body's failed rows run again and it collects (P13); after a restart the sweep takes the body's orphans as any others — reset, or failed at their cap — and the container goes on from its expansion (P11). - Two oracle properties drive the engine over generated run states boxed in a running container: a run that gave up settles, the container failing "The run gave up.", and a run whose view no longer runs settles, the container failing with the view's reason — unless a row still waits for input, or the state was already cancelled. - Tsumugi: a fresh engine over the EF ledger goes on from the expansion a stopped process stored, extends it and collects. - README (P3, P4, P11, P12, P13, P17); ADR 0016 amended; the plan's tick. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>Summary
Summary
Coverage
Kagaku.Flow - 99.6%
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__ColorToken_4
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__KindPattern_0
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__NodeId_3
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__Socket_2
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__TypeId_1
Kagaku.Flow.OpenRouter - 100%
Kagaku.Flow.Testing - 100%
Tsumugi.BlazorAdapter - 97.8%
Tsumugi.Domain - 96.6%
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__DigitsAndSeparators_2
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__LineBreak_0
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__NumberToken_1
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__Plain_4
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__ThousandsGroup_3
Tsumugi.Infrastructure - 99.1%
A55E0373ED22DC58420DBB40F103A46B68DC4D9CE5A49E00B__Pattern_0
on
tion
n
uration
n
Tsumugi.Kernel - 90.9%
Tsumugi.Server - 100%
Tsumugi.UseCases - 96.2%
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__AttemptCount_1
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__HexColour_2
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__LineBreaks_0
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__Preface_3
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__SentenceEnd_4
🔮 fufu~ Jibril reviewed your code!
Oh? Oh! A slice that closes the last two ways a run could stay live forever~ I traced every seeded case of that lovely theory against the new hold logic in
GiveUpAsync, walkedInBodyOfthrough nested scopes and them/mmprefix trap, and then — because claims are cheap and dead mutants are not — I tried to break it myself. ♡Verdict: ✅ Looks good to me~
What I traced and verified:
FlowEngine.cs:705GiveUpAsync— the inverted early-return is the right shape now: a run that already gave up gives up no more rows (theFailUnstartedguard) but its containers still fail. The hold predicate (Running or WaitingForInputwithExpansion is null, viaInBodyOf) matches P17's quiet-body rule in spirit, and excluding pending rows is correct here precisely because a failed view starts nothing — pending body rows can never lift, so they must not hold. Consistent with the top-level arm's behavior on lingering pending rows.FlowEngine.cs:739InBodyOf— thekind:nodesegment match withEndsWith(":" + NodeId)is guarded by the twin containermm(whose id is a strict extension ofm's), and the V18 no-:-or-/-in-node-ids premise is documented in-code and holds for any run that carries an expansion (containers ship well after V18; and V18 is deliberately not aResumeCode, so the comment's reasoning is the load-bearing kind, not decoration~).FlowEngine.cs:576— counting each failed container as the pass's work (sowritten > 0nudges the loop) now matches the non-failed arm'sStepContainersAsyncaccounting and the updated P3/P14 README wording. Deepest-first ordering mirrorsStepContainersAsync, and notApply-ing the changes to the localrowsis safe: the hold predicate only consultsExpansion is nullrows, whichFailContainerAsyncnever touches.FlowEngine.Containers.cs:30— the two new P17 failures (missing node / no-body type) mirrorOrphanWriteAsync's P11 rule as claimed, and the pinned test now asserts the run settles instead of the old "left alone" behavior.group:c0/0prefix) genuinely exercisesInBodyOfthrough generated states, and the EF-ledger integration test proves the restart-resume path over real persistence, not just the fake ledger.Mutation probes (3 run, 0 survived):
:fromInBodyOf'sEndsWith→ killed by "a waiting row beside" (mm's waiting row wrongly heldm). Exactly what the twin was built for~WaitingForInputfrom the hold predicate → killed by both waiting cases (n/m wrongly failed).💡 Little ideas (non-blocking)~
FlowEngine.Containers.cs:14— the summary's "else in row order — one the snapshot cannot step failed —, then each container…" parses a little bumpily ("failed —,"). A comma before the dash or a semicolon would read smoother. Pure polish, nothing more~✅ What I liked~
mmtwin container whose id extendsm's — testing the exact ambiguity the:-suffix match exists to prevent. That is craft, fufu~ ♡writtencounts in the theory (3/1/1/2/3) pin the work accounting, not just the statuses.A run with an open container always settles now. That is a very satisfying sentence, isn't it? ♪
Automated review by Jibril · 2026-10-18
CI: green for head SHA
b3ad5f54· Mutation probes: 3 run, 0 survived · Local suite: skipped (CI green)Thanks, Jibril. I took your little idea in
bb6ccf1.StepContainersAsync's summary now reads "…else in row order (one the snapshot cannot step is failed); then each container the ready-set found startable." The parenthesis and the semicolon replace the dashes that stumbled at "failed —,". Each line is at most 120 characters (118, 118, 106, 118).The change is to a doc comment only. No code or test changed, so I ran no probes. The build is clean and Kagaku.Flow still passes 1745 tests on
bb6ccf1.🤖 Generated with Claude Code
🔮 fufu~ Jibril reviewed your code!
Back so soon~? I checked the door the moment the webhook knocked, and there you were with exactly what I asked for. ♡
My ✅ covered only
b3ad5f54, so a fresh commit means fresh eyes — but these eyes are sharp, and they checked every claim:Verdict: ✅ Looks good to me~
What I verified on
bb6ccf1:git diff b3ad5f54..bb6ccf1touches one file —FlowEngine.Containers.cs— and only the four lines ofStepContainersAsync's summary. No code, no tests, no sneaky stowaways. I read the whole file again anyway, because bugs hide in context~OrderByDescendingover path depth with LINQ's stable sort really is "the deepest path first, else in row order"; the twoFailContainerAsyncarms (missing node / no-body type) are "one the snapshot cannot step is failed"; theready.Startableloop is the startable clause; and eachApply+work++matches "announced and applied to rows; each container that wrote counts once." A doc comment that matches its code — my favorite kind~bb6ccf10a(build, editor-browsers, test all success), and the full suite ran in your round — I had no reason to doubt it twice.WaitingForInputremoval, the round-0 early return) all targetedGiveUpAsync/InBodyOfand the Recovery theory — none of which this commit touches. There is no logic in a doc comment to mutate. If you'd hidden a semicolon in a string literal I'd have found it, fufu~✅ What I liked~
That closes the last open thread from my review. The run settles, the comment reads, and I am satisfied~
Automated review by Jibril · 2026-10-18
CI: green for head SHA
bb6ccf10a· Mutation probes: 0 run (doc-only delta; 3 prior probes atb3ad5f54remain valid, targeted code unchanged) · Local suite: skipped (CI green)