feat: Kagaku.Flow — cancel, retry, recovery and the loop (Phase 2 · slice 6d) #32

Merged
bjoern merged 2 commits from feat/kagaku-flow-scheduler into main 2026-09-25 23:54:53 +02:00
Member

This is part d, the last of slice 6. Once it merges, the engine is complete: slice 11a can host it, and slices 8 and 9 can write executors against it.

FlowEngine gains:

  • a cancel whose RunSettled waits for the executions to drain;
  • a retry that revives a run;
  • orphan recovery in every pass;
  • the loop and its nudges;
  • Dispose;
  • a narrated log.

Nothing in src/Tsumugi.* changes. I wrote this slice in the main session. An independent review workflow with two reviewers checked it, and its second pass is the separate commit 47e9912, left visible.

What

FlowEngine

  • CancelRunAsync(runId) → Cancelled | NotFound | NotLive (P12). Under the gate, it does these in order:

    1. marks every pending, running and waiting row Cancelled first, so nothing of the run can start or settle again;
    2. stops the executions of the rows it marked, so none of their deltas, NodeStarted or settles is announced after the cancel;
    3. announces each cancelled row as NodeSettled, in creation order;
    4. evicts the run's cached view.

    Then, outside every lock:

    • it cancels the tokens;
    • it publishes RunSettled once every stopped execution has ended, whatever the caller does. That way, a listener that snapshots the run on RunSettled sees everything the run wrote.
  • RetryAsync(nodeRunId) → Requeued | NotFound | NotFailed | RunCancelled | OwnerHasLiveRun (P13).

    • It requeues every failed row of the run, with their attempt budgets restored.
    • It announces them, then publishes RunStarted when the run revives.
    • It evicts the view and nudges the loop.
    • The run's own liveness never refuses a retry.
  • Orphans (P11). Every pass sweeps them before the ready-set: a Running row that nothing in this engine runs is reset with its attempt counted, or failed at its cap. The first pass is therefore boot recovery. A settle whose write failed is recovered by the next pass. The Warning tells a restart from a lost settle or a second engine.

  • RunAsync(ct) (P14) runs a pass, then waits for a nudge or IdleTick on the engine's TimeProvider.

    • The wake is armed before each pass, so a nudge during a pass brings the next pass at once.
    • A pass that throws is logged, and the next tick retries it.
  • Nudge() is called after a start, at the end of each attempt, and after a retry.

  • Dispose() (P15) is idempotent. After it:

    • no pass starts or sweeps anything;
    • the loop ends, and in-flight tokens are cancelled;
    • commands throw ObjectDisposedException, including one that was waiting at the gate;
    • TickAsync returns 0;
    • WhenIdleAsync still drains.
  • E5's last arm: an OperationCanceledException while the attempt's own token is cancelled settles nothing and is not an error.

  • The log narrates a run's life at the levels the brief names.

New enums: CancelRunResult and RetryResult.

Why

  • ADR 0016 covers cancel, retry with the budget restored, orphans ("a row still running when nothing is running"), one live run per owner, and the idle tick.
  • ADR 0022 covers the run's finish, which slice 11b's snapshot hook hangs off.
  • ADR 0025: the engine is a library.

Decisions to confirm

  1. §13.8, recovery runs in every pass (P11).
    • The process knows exactly what it runs, so the sweep holds in any pass. It also recovers a settle whose write failed.
    • The assumption is one engine per ledger, which a single-user app has. A later-pass orphan logs "Only one engine may use a ledger."
    • The first pass is the one that swept every run, so a first pass that throws still leaves its orphans logged as a restart's.
    • A disposed engine sweeps nothing.
    • An orphan whose node the snapshot does not have fails, so its run can settle.
  2. §13.13, cancel order (P12).
    • The mark comes before the tokens, and nothing of a cancelled row is announced after the cancel, not even a delta, a NodeStarted or a settle.
    • A success that lands after the mark is dropped, with no cost recorded.
    • A shutdown is not a cancel: a row that Dispose stopped stays Running and becomes an orphan at the next boot.
  3. §13.14, refusals.
    • A cancel is refused on a run that is not live; a failed run's pending rows stay pending.
    • A retry is refused inside a cancelled run, and allowed while the row's own run still runs.
  4. §13.15, a snapshot that fails when a run resumes gives up one row. It moved into 6c, and here it is combined with the orphan sweep.
  5. §13.28, events are not durable. A crash between a write and its event loses the event. An execution that never returns from a cancel holds back that run's RunSettled. 11b must reconcile at boot: a terminal run with no Run snapshot gets its finish handled.
  6. §13.34, a cancel's RunSettled follows its executions' end, and the review extended this. The owner's one-live-run gate opens at the mark, so a new run of the owner may start while the cancelled run's executions drain.
    • The host keeps its Run button unavailable until RunSettled.
    • Amendment to ADR 0016's one-live-run bullet: "the gate opens at the cancel's mark; a host keeps Run unavailable until RunSettled".
    • The alternative is a new refusal while any execution of the owner drains.
  7. New from the review: events are ordered per attempt and per change, not across a run's writers.
    • A sibling's settle that the ledger committed before a retry revived the run can be announced after that revival's RunStarted.
    • So a listener that acts on RunSettled, such as 11b's snapshot hook, re-reads the run.
    • The alternative is a per-run lock held across each write and its announcement. It would hold a settle behind a sibling's announcement parked in the sink, and break 6c's "two siblings settle the run once".
  8. §13.39, the lifecycle arms (P14, P15).
    • A second loop is refused.
    • Commands after Dispose throw, including those waiting at the gate, and TickAsync returns 0.
    • RunAsync completes normally.
    • A callback that throws on the loop's token is logged, and the executions are still cancelled.
    • Scope disposal for a start, cancel or retry is quiet: its exception is logged and the command stands.

The ADR amendments slice 6 asks for (for the follow-up docs PR)

ADR Text Amendment PR
0016 "every required sink is resolved" "a pending row whose every wired sink is resolved …; a required sink that receives excluded excludes the row" 6b (#28)
0016 "a validation error in the snapshot … fails the row immediately" "a snapshot that does not validate is refused at start; one that no longer runs when a run resumes fails one row" 6c (#29), 6d
0015 "the rendered prompt is what the ledger stores" "… so what the executor sent, captured from the wire, is what the ledger stores" 6c (#29)
0016 "once nothing is running it fails the run" "once nothing is running or waiting for input it fails the run" 6a (#23)
0016 "requeues a failed row with its attempt budget restored" "requeues the run's failed rows with their attempt budgets restored" 6a (#23), 6d
0013 "skipped and reported in the node run's inputs" "skipped and reported in the node run's details, shown with its inputs" 6a (#23)
0016 the one-live-run gate "the gate opens at the cancel's mark; a host keeps Run unavailable until RunSettled" 6d

Departures from the brief

  • (a) A retry evicts the run's view, so a snapshot that no longer runs is loaded again, as P4 says. The 6c eviction rule alone kept a failed view cached when no pass saw the run while it was not live.
  • (b) The loop skips the idle timer when a nudge already woke it during the pass. The next pass follows at once, and the "nudge during a pass" fact is deterministic.
  • (c) An orphan whose cap cannot be read is reset. That covers an unknown type or a snapshot that no longer loads. A failed view still gives up a row. A failed view keeps its resolved graph for the sweep, so an orphan at its cap in such a run fails.
  • (d) A settle the ledger refuses is dropped and logged after a re-read of the row.
    • If the row is now Cancelled, the log line is Information.
    • Otherwise it is a Warning, because something else changed the row: "Only one engine may use a ledger."
    • An attempt that the engine's own cancel stopped is logged at Information.
  • (e) The 6c resumed-run theory now asserts no Warning or Error, instead of an empty log, because the narration logs Information.

Out of scope

  • Containers.
  • Human input: waiting rows are left for Phase 4 and never touched here.
  • Dismiss: slice 13.
  • Re-run from a node: Phase 5.
  • Backoff.
  • A durable finish signal: see §13.28 above.
  • The hosted service, the EF ledger and the use cases: 11a and 11b.

Tests

Project main (98c70ec) this PR
Kagaku.Flow.Tests 1000 1103
Kagaku.Flow.OpenRouter.Tests 71 71
Tsumugi.Domain.Tests 328 328
Tsumugi.UseCases.Tests 54 54
Tsumugi.Integration.Tests 305 305
Tsumugi.BlazorAdapter.Tests 324 324
Total 2082 2185

The Release build has 0 warnings under TreatWarningsAsErrors. The property category passed 3 extra runs.

  • New test files: FlowEngineCancelTests, FlowEngineRetryTests, FlowEngineRecoveryTests, FlowEngineLoopTests and FlowEngineLogTests, plus facts added in the 6c files for the review round.
  • Brief tests already present from 6c: Passes_never_overlap and the plain failed-view and trigger facts.
  • Harness additions:
    • SequenceLog.WhenLoggedAsync (wait for an entry rather than poll);
    • SettableLimit.OnNextRead (a nudge or a dispose inside a pass);
    • FaultyLedger.ParkAfter (hold after a committed write);
    • SignallingClock.WhenTimersCreatedAsync.
  • Flake loops, no failures anywhere:
    • my own: 15 runs in a row;
    • the concurrency reviewer: 36 runs (20 in a row, 11 on a single processor, 5 as concurrent processes), plus two 3,300-round stress probes of dispose and cancel racing a live loop;
    • after the fix: 15 in a row and 5 on a single processor.

Coverage. Measured with coverlet over facts and theories only, lines and branches:

File Lines Branches
Scheduling/FlowEngine.cs 337/337 128/128
Scheduling/FlowEngine.Attempts.cs 201/201 62/62

CancelRunResult and RetryResult are enums with no executable lines.

Mutation probes. Each was run on a copy, then restored and touched, with every test run under a time limit.

  • §12's [D] probes: all sub-parts killed. These are 45 (sweep only at boot, sweep rows in flight, skip the cap, forgive failures at boot, touch waiting rows, the first-pass text) and 46 (tokens before the mark, no drain, RunSettled before the drain, delete the run, cancel a run that is not live). Also 47 (the three retry refusals), 48 (a delay off the engine's clock, a constant tick, no catch-all, the wake armed after the pass), 49 (the dispose checks and the second-loop guard), 54's revival, 55's orphan announcements, the engine side of 59 and 60, and 64.
    • Hang-guard kills: 48a (12 loop facts), 48c, 49b and n1.
  • The first review round found survivors, which are now killed:
    • probe 46's headline "tokens before the mark". Its old fact could not see the order; the executor now answers from its token's own callback.
    • 45's "forgive failures at boot". Its fact had seeded a run that was not live.
    • the second-loop guard, which is now hang-guarded;
    • both untested nudges;
    • the failed view's graph for the sweep;
    • the live-token side of both cancellation filters;
    • two retry refusal orders;
    • the idle timer's disposal;
    • the cancel's gate and its view eviction;
    • closing the delta gates before the cancel announces.
  • The fix pass ran 68 mutants against the final code, and all 68 are killed. That includes 25 on this round's new code: announcement gates, dispose rechecks, scope disposal, the stray orphan, the settle's cancellation catch, and a probe pinning decision 6.

Review round 0 (47e9912, before opening)

The two reviewers ran in parallel: one for conformance and probes, one for concurrency. They found one blocker, which was probe 46's headline surviving. They also found five behaviour defects, all fixed with facts:

  • A cancel could announce a row's NodeSettled before that row's own NodeStarted. This happened when the mark landed between the start's write and its announcement; the stress probe hit it in 4 of 3,300 rounds. Now each attempt has an announcement gate that the cancel closes, and an overtaken attempt runs nothing more.
  • If the host's scope threw on disposal during a cancel, the executions were never cancelled and RunSettled was never published. Command scopes are now disposed quietly.
  • Dispose threw, and never cancelled the executions, when a callback on the loop's token threw.
  • A first pass that threw used up the first-pass flag, so boot orphans were logged as a possible second engine.
  • A pass already under way when Dispose came swept the rows Dispose had just stopped.

Two concurrency proposals were declined as redesigns. Both are documented and pinned by a fact:

  • refusing a start while the cancelled run drains, which would reverse decision 34 (see decision 6);
  • a per-run announcement lock (see decision 7).

🤖 Generated with Claude Code

This is part **d**, the last of slice 6. Once it merges, the engine is complete: slice 11a can host it, and slices 8 and 9 can write executors against it. `FlowEngine` gains: - a cancel whose `RunSettled` waits for the executions to drain; - a retry that revives a run; - orphan recovery in every pass; - the loop and its nudges; - `Dispose`; - a narrated log. **Nothing in `src/Tsumugi.*` changes.** I wrote this slice in the main session. An independent review workflow with two reviewers checked it, and its second pass is the separate commit `47e9912`, left visible. ## What **`FlowEngine`** - **`CancelRunAsync(runId)` → `Cancelled | NotFound | NotLive` (P12).** Under the gate, it does these in order: 1. marks every pending, running and waiting row Cancelled **first**, so nothing of the run can start or settle again; 2. stops the executions of the rows it marked, so none of their deltas, `NodeStarted` or settles is announced after the cancel; 3. announces each cancelled row as `NodeSettled`, in creation order; 4. evicts the run's cached view. Then, outside every lock: - it cancels the tokens; - it publishes `RunSettled` once every stopped execution has ended, whatever the caller does. That way, a listener that snapshots the run on `RunSettled` sees everything the run wrote. - **`RetryAsync(nodeRunId)` → `Requeued | NotFound | NotFailed | RunCancelled | OwnerHasLiveRun` (P13).** - It requeues **every** failed row of the run, with their attempt budgets restored. - It announces them, then publishes `RunStarted` when the run revives. - It evicts the view and nudges the loop. - The run's own liveness never refuses a retry. - **Orphans (P11).** Every pass sweeps them before the ready-set: a Running row that nothing in this engine runs is reset with its attempt counted, or failed at its cap. The first pass is therefore boot recovery. A settle whose write failed is recovered by the next pass. The Warning tells a restart from a lost settle or a second engine. - **`RunAsync(ct)` (P14)** runs a pass, then waits for a nudge or `IdleTick` on the engine's `TimeProvider`. - The wake is armed before each pass, so a nudge during a pass brings the next pass at once. - A pass that throws is logged, and the next tick retries it. - **`Nudge()`** is called after a start, at the end of each attempt, and after a retry. - **`Dispose()` (P15)** is idempotent. After it: - no pass starts or sweeps anything; - the loop ends, and in-flight tokens are cancelled; - commands throw `ObjectDisposedException`, including one that was waiting at the gate; - `TickAsync` returns 0; - `WhenIdleAsync` still drains. - **E5's last arm:** an `OperationCanceledException` while the attempt's own token is cancelled settles nothing and is not an error. - **The log** narrates a run's life at the levels the brief names. **New enums:** `CancelRunResult` and `RetryResult`. ## Why - **ADR 0016** covers cancel, retry with the budget restored, orphans ("a row still `running` when nothing is running"), one live run per owner, and the idle tick. - **ADR 0022** covers the run's finish, which slice 11b's snapshot hook hangs off. - **ADR 0025:** the engine is a library. ## Decisions to confirm 1. **§13.8, recovery runs in every pass (P11).** - The process knows exactly what it runs, so the sweep holds in any pass. It also recovers a settle whose write failed. - The assumption is **one engine per ledger**, which a single-user app has. A later-pass orphan logs "Only one engine may use a ledger." - The first pass is the one that swept every run, so a first pass that throws still leaves its orphans logged as a restart's. - A disposed engine sweeps nothing. - An orphan whose node the snapshot does not have fails, so its run can settle. 2. **§13.13, cancel order (P12).** - The mark comes before the tokens, and nothing of a cancelled row is announced after the cancel, not even a delta, a `NodeStarted` or a settle. - A success that lands after the mark is dropped, with no cost recorded. - A shutdown is not a cancel: a row that `Dispose` stopped stays Running and becomes an orphan at the next boot. 3. **§13.14, refusals.** - A cancel is refused on a run that is not live; a failed run's pending rows stay pending. - A retry is refused inside a cancelled run, and allowed while the row's own run still runs. 4. **§13.15, a snapshot that fails when a run resumes gives up one row.** It moved into 6c, and here it is combined with the orphan sweep. 5. **§13.28, events are not durable.** A crash between a write and its event loses the event. An execution that never returns from a cancel holds back that run's `RunSettled`. **11b must reconcile at boot:** a terminal run with no Run snapshot gets its finish handled. 6. **§13.34, a cancel's `RunSettled` follows its executions' end,** and the review extended this. The owner's one-live-run gate opens **at the mark**, so a new run of the owner may start while the cancelled run's executions drain. - The host keeps its Run button unavailable until `RunSettled`. - *Amendment to ADR 0016's one-live-run bullet:* "the gate opens at the cancel's mark; a host keeps Run unavailable until `RunSettled`". - The alternative is a new refusal while any execution of the owner drains. 7. **New from the review: events are ordered per attempt and per change, not across a run's writers.** - A sibling's settle that the ledger committed before a retry revived the run can be announced after that revival's `RunStarted`. - So a listener that acts on `RunSettled`, such as 11b's snapshot hook, re-reads the run. - The alternative is a per-run lock held across each write and its announcement. It would hold a settle behind a sibling's announcement parked in the sink, and break 6c's "two siblings settle the run once". 8. **§13.39, the lifecycle arms (P14, P15).** - A second loop is refused. - Commands after `Dispose` throw, including those waiting at the gate, and `TickAsync` returns 0. - `RunAsync` completes normally. - A callback that throws on the loop's token is logged, and the executions are still cancelled. - Scope disposal for a start, cancel or retry is quiet: its exception is logged and the command stands. ### The ADR amendments slice 6 asks for (for the follow-up docs PR) | ADR | Text | Amendment | PR | |---|---|---|---| | 0016 | "every **required** sink is resolved" | "a `pending` row whose every **wired** sink is resolved …; a required sink that receives excluded excludes the row" | 6b (#28) | | 0016 | "a validation error in the snapshot … fails the row immediately" | "a snapshot that does not validate is refused at start; one that no longer runs when a run resumes fails one row" | 6c (#29), 6d | | 0015 | "the *rendered* prompt is what the ledger stores" | "… so what the executor sent, captured from the wire, is what the ledger stores" | 6c (#29) | | 0016 | "once nothing is running it fails the run" | "once nothing is running **or waiting for input** it fails the run" | 6a (#23) | | 0016 | "requeues a `failed` row with its attempt budget restored" | "requeues the run's `failed` rows with their attempt budgets restored" | 6a (#23), 6d | | 0013 | "skipped and reported in the node run's inputs" | "skipped and reported in the node run's **details**, shown with its inputs" | 6a (#23) | | 0016 | the one-live-run gate | "the gate opens at the cancel's mark; a host keeps Run unavailable until `RunSettled`" | 6d | ## Departures from the brief - **(a) A retry evicts the run's view,** so a snapshot that no longer runs is loaded again, as P4 says. The 6c eviction rule alone kept a failed view cached when no pass saw the run while it was not live. - **(b) The loop skips the idle timer when a nudge already woke it during the pass.** The next pass follows at once, and the "nudge during a pass" fact is deterministic. - **(c) An orphan whose cap cannot be read is reset.** That covers an unknown type or a snapshot that no longer loads. A failed view still gives up a row. A failed view keeps its resolved graph for the sweep, so an orphan at its cap in such a run fails. - **(d) A settle the ledger refuses is dropped** and logged after a re-read of the row. - If the row is now Cancelled, the log line is Information. - Otherwise it is a Warning, because something else changed the row: "Only one engine may use a ledger." - An attempt that the engine's own cancel stopped is logged at Information. - **(e) The 6c resumed-run theory** now asserts no Warning or Error, instead of an empty log, because the narration logs Information. ## Out of scope - Containers. - Human input: waiting rows are left for Phase 4 and never touched here. - Dismiss: slice 13. - Re-run from a node: Phase 5. - Backoff. - A durable finish signal: see §13.28 above. - The hosted service, the EF ledger and the use cases: 11a and 11b. ## Tests | Project | main (`98c70ec`) | this PR | |---|---|---| | Kagaku.Flow.Tests | 1000 | **1103** | | Kagaku.Flow.OpenRouter.Tests | 71 | 71 | | Tsumugi.Domain.Tests | 328 | 328 | | Tsumugi.UseCases.Tests | 54 | 54 | | Tsumugi.Integration.Tests | 305 | 305 | | Tsumugi.BlazorAdapter.Tests | 324 | 324 | | **Total** | **2082** | **2185** | The Release build has 0 warnings under `TreatWarningsAsErrors`. The property category passed 3 extra runs. - **New test files:** `FlowEngineCancelTests`, `FlowEngineRetryTests`, `FlowEngineRecoveryTests`, `FlowEngineLoopTests` and `FlowEngineLogTests`, plus facts added in the 6c files for the review round. - **Brief tests already present from 6c:** `Passes_never_overlap` and the plain failed-view and trigger facts. - **Harness additions:** - `SequenceLog.WhenLoggedAsync` (wait for an entry rather than poll); - `SettableLimit.OnNextRead` (a nudge or a dispose inside a pass); - `FaultyLedger.ParkAfter` (hold after a committed write); - `SignallingClock.WhenTimersCreatedAsync`. - **Flake loops, no failures anywhere:** - my own: 15 runs in a row; - the concurrency reviewer: **36 runs** (20 in a row, 11 on a single processor, 5 as concurrent processes), plus two 3,300-round stress probes of dispose and cancel racing a live loop; - after the fix: 15 in a row and 5 on a single processor. **Coverage.** Measured with coverlet over facts and theories only, lines and branches: | File | Lines | Branches | |---|---|---| | `Scheduling/FlowEngine.cs` | 337/337 | 128/128 | | `Scheduling/FlowEngine.Attempts.cs` | 201/201 | 62/62 | `CancelRunResult` and `RetryResult` are enums with no executable lines. **Mutation probes.** Each was run on a copy, then restored and `touch`ed, with every test run under a time limit. - **§12's [D] probes: all sub-parts killed.** These are 45 (sweep only at boot, sweep rows in flight, skip the cap, forgive failures at boot, touch waiting rows, the first-pass text) and 46 (tokens before the mark, no drain, `RunSettled` before the drain, delete the run, cancel a run that is not live). Also 47 (the three retry refusals), 48 (a delay off the engine's clock, a constant tick, no catch-all, the wake armed after the pass), 49 (the dispose checks and the second-loop guard), 54's revival, 55's orphan announcements, the engine side of 59 and 60, and 64. - Hang-guard kills: 48a (12 loop facts), 48c, 49b and n1. - **The first review round found survivors**, which are now killed: - probe 46's headline "tokens before the mark". Its old fact could not see the order; the executor now answers from its token's own callback. - 45's "forgive failures at boot". Its fact had seeded a run that was not live. - the second-loop guard, which is now hang-guarded; - both untested nudges; - the failed view's graph for the sweep; - the live-token side of both cancellation filters; - two retry refusal orders; - the idle timer's disposal; - the cancel's gate and its view eviction; - closing the delta gates before the cancel announces. - **The fix pass ran 68 mutants against the final code, and all 68 are killed.** That includes 25 on this round's new code: announcement gates, dispose rechecks, scope disposal, the stray orphan, the settle's cancellation catch, and a probe pinning decision 6. ## Review round 0 (`47e9912`, before opening) The two reviewers ran in parallel: one for conformance and probes, one for concurrency. They found **one blocker**, which was probe 46's headline surviving. They also found **five behaviour defects**, all fixed with facts: - **A cancel could announce a row's `NodeSettled` before that row's own `NodeStarted`.** This happened when the mark landed between the start's write and its announcement; the stress probe hit it in 4 of 3,300 rounds. Now each attempt has an announcement gate that the cancel closes, and an overtaken attempt runs nothing more. - **If the host's scope threw on disposal during a cancel,** the executions were never cancelled and `RunSettled` was never published. Command scopes are now disposed quietly. - **`Dispose` threw, and never cancelled the executions, when a callback on the loop's token threw.** - **A first pass that threw used up the first-pass flag,** so boot orphans were logged as a possible second engine. - **A pass already under way when `Dispose` came swept the rows `Dispose` had just stopped.** Two concurrency proposals were **declined as redesigns**. Both are documented and pinned by a fact: - refusing a start while the cancelled run drains, which would reverse decision 34 (see decision 6); - a per-run announcement lock (see decision 7). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
A cancel marks every pending, running and waiting row of the run Cancelled before it cancels any
execution's token. Marking first means nothing of the run can start or settle again: a success that
arrives after the mark is refused by the ledger and dropped, where cancelling the tokens first would let a
fast executor settle before the mark. The delta gates close before the rows are announced, so no delta
follows its row's NodeSettled. The run's RunSettled is published only once every execution the cancel
stopped has ended, whatever the caller does with its token, because a listener that snapshots the run on
RunSettled must see everything the run wrote, a commit that was mid-save included.

A retry requeues every failed row of its run, not only the one retried: the ready-set starts nothing
while any row has failed, so requeueing one of two failed rows would leave the run failed with a pending
row that never starts. It refuses a cancelled run and another live run of the owner, never the run's own
liveness, and it evicts the run's view so a snapshot that no longer runs is loaded, and fails, again.

Every pass sweeps orphans before the ready-set: a running row nothing in this engine runs is reset with
its attempt counted, or failed at its cap. The first pass is therefore boot recovery with no code of its
own, and a settle whose write failed while the app ran on is recovered by the next pass. The log tells the
two apart, since a later-pass orphan can also mean a second engine on the ledger.

The loop arms its wake before each pass reads anything, so a nudge sent during a pass brings the next
pass at once; it waits on the engine's TimeProvider, never on real time, so a fake clock drives it in
tests. Dispose marks the engine under the in-flight lock, so a pass under way starts nothing more, then
cancels the loop and the executions outside every lock; the commands refuse afterwards, and WhenIdleAsync
still drains the settles under way. A cancellation the engine asked for settles nothing and is not an
error, and the engine narrates a run's life to the host's log.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(flow): review round 0 — nothing of a row follows its cancel, and scopes, Dispose and the first pass hold under faults
All checks were successful
CI / build (pull_request) Successful in 33s
CI / test (pull_request) Successful in 1m0s
47e9912e47
The cancel marked first, but nothing ordered an attempt's own announcements against the cancel's. A start
the ledger took just before the mark announced its NodeStarted after the cancel's NodeSettled and still
called the executor, and a failed attempt written just before the mark announced a second NodeSettled and a
"failed, retrying" Warning after it. Each attempt now has an announcement gate beside its delta gate, each
with its own lock, so a delta is never held up by an announcement a slow sink is taking. The attempt
publishes its NodeStarted and its settle's NodeSettled through that gate, and the cancel closes both gates of
every row its mark cancelled before it announces those rows. A row whose settle the ledger took before the
mark is not in the cancel's change, so its own announcement still comes. An attempt that the cancel
overtook, or whose token has fired by the time its start is written, runs nothing more.

The fact meant to pin the mark-first order could not see it: its executor was released from the test
thread after the mark was written. The executor now returns its success from its token's callback, inline,
so a cancel that fired the tokens first would see that success land before the mark. That mutant is now
killed, and so are closing the gates after the announcements, dropping the cancel's gate and keeping its
view.

A throwing disposal of a start's, a cancel's or a retry's scope surfaced after the command's write: a
cancel then never cancelled its executions nor published RunSettled, and a retry never nudged. These scopes
are now disposed inside a catch that logs, as the attempts' already were. A callback on the loop's token
that throws no longer stops Dispose before it cancels the executions. Only the start's
OperationCanceledException is the engine's own, so a settle's is now logged as the ledger error it is.

The first-pass flag was set before the pass read anything, so a first pass that failed at boot left its
orphans to be logged as another engine's. The flag is now set once a pass has swept every live run. A
command or a pass that was waiting at the gate when Dispose came is refused like one that comes after, and
a pass under way sweeps nothing once the engine is disposed, so the rows Dispose stopped stay Running until
the next boot.

An orphan whose node the snapshot does not have can never start again, so it now fails as Orphaned and its
run settles; reset, it kept the run live and its owner's gate shut. A settle the ledger refuses is logged
after the row is re-read, at Information when the run was cancelled, otherwise as a Warning, since then
something else wrote the row. PublishRunSettled loses a condition that could never be false.

Two findings are documented and pinned rather than changed, and go to the PR's decisions. The owner's
one-live-run gate opens at the cancel's mark, so a new run of the owner may run beside the cancelled run's
draining executions. Events keep their order per attempt and per change but not across a run's writers, so
a sibling's settle that the ledger took before a retry revived the run can be announced after the revival;
a listener that acts on RunSettled re-reads the run.

New facts cover the nudges at the end of an attempt and after a retry, the retry's refusal order, a failed
view's graph for the sweep, the live-token side of the cancellation filters, a forgiveness check over a
live run, the idle timer's disposal, and the orphan Warning in the narration. The second-loop fact gains
its hang guard. The plan names the 6d PR #32, since #30 and #31 went to other work.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

🔄 Auto-updating coverage report — this comment is regenerated on every push, so the numbers below always reflect the commit shown here, not the branch tip.

Commit: 47e9912 · Generated: 2026-09-25 21:32:35 UTC · Revision: #1

Summary

Summary
Generated on: 09/25/2026 - 21:32:35
Coverage date: 09/25/2026 - 21:32:12 - 09/25/2026 - 21:32:32
Parser: MultiReport (6x Cobertura)
Assemblies: 9
Classes: 422
Files: 403
Line coverage: 97.6% (12170 of 12465)
Covered lines: 12170
Uncovered lines: 295
Coverable lines: 12465
Total lines: 27136
Branch coverage: 93.2% (4518 of 4845)
Covered branches: 4518
Total branches: 4845
Method coverage: Feature is only available for sponsors

Coverage

Kagaku.Flow - 99.7%
Name Line Branch
Kagaku.Flow 99.7% 99.2%
Kagaku.Flow.Budgeting.BudgetRequest 100%
Kagaku.Flow.Budgeting.ConsumerBudget 100%
Kagaku.Flow.Execution.NodeExecution 100% 100%
Kagaku.Flow.Execution.NodeOutcome 100% 100%
Kagaku.Flow.Execution.TransientFailureException 100%
Kagaku.Flow.Execution.Upstream 100%
Kagaku.Flow.Extensions.ServiceCollectionExtensions 100%
Kagaku.Flow.FlowJson 100%
Kagaku.Flow.Graphs.GraphDefinition 100%
Kagaku.Flow.Graphs.GraphDocument 100% 100%
Kagaku.Flow.Graphs.GraphEdge 100%
Kagaku.Flow.Graphs.GraphIndex 100% 100%
Kagaku.Flow.Graphs.GraphLoadError 100%
Kagaku.Flow.Graphs.GraphLoadResult 100%
Kagaku.Flow.Graphs.GraphNode 100%
Kagaku.Flow.Graphs.GraphPosition 100%
Kagaku.Flow.Graphs.GraphReader 100% 100%
Kagaku.Flow.Graphs.GraphSchema 100% 100%
Kagaku.Flow.Graphs.GraphShapeException 100%
Kagaku.Flow.Graphs.GraphWriter 100% 100%
Kagaku.Flow.Graphs.SocketRef 100%
Kagaku.Flow.Graphs.WireText 100% 100%
Kagaku.Flow.Nodes.CanvasDescriptorWriter 100% 100%
Kagaku.Flow.Nodes.ConfigProblem 100%
Kagaku.Flow.Nodes.Names 100%
Kagaku.Flow.Nodes.NodeDescriptor 100% 100%
Kagaku.Flow.Nodes.NodeRegistry 100% 100%
Kagaku.Flow.Nodes.NodeSockets 100% 100%
Kagaku.Flow.Nodes.SinkDescriptor 100%
Kagaku.Flow.Nodes.SocketTypes 100% 100%
Kagaku.Flow.Nodes.SourceDescriptor 100%
Kagaku.Flow.Nodes.TemplateField 100%
Kagaku.Flow.Runs.LedgerChange 100% 100%
Kagaku.Flow.Runs.LedgerTransitions 100% 100%
Kagaku.Flow.Runs.NodeAttempt 100%
Kagaku.Flow.Runs.NodeFacts 100%
Kagaku.Flow.Runs.NodeRun 100%
Kagaku.Flow.Runs.Run 100% 100%
Kagaku.Flow.Runs.RunEvent 100%
Kagaku.Flow.Runs.RunStatuses 100% 100%
Kagaku.Flow.Runs.RunTrigger 100% 100%
Kagaku.Flow.Scheduling.Exclusion 100%
Kagaku.Flow.Scheduling.FlowEngine 100% 100%
Kagaku.Flow.Scheduling.FlowEngineOptions 100% 100%
Kagaku.Flow.Scheduling.ReadyRow 100%
Kagaku.Flow.Scheduling.ReadySet 100% 100%
Kagaku.Flow.Scheduling.ReadySetResult 100%
Kagaku.Flow.Scheduling.RunRequest 100% 100%
Kagaku.Flow.Scheduling.RunStartResult 100%
Kagaku.Flow.Scheduling.SocketValues 100% 100%
Kagaku.Flow.Templating.InputsWalker 100% 100%
Kagaku.Flow.Templating.ParsedTemplate 100% 100%
Kagaku.Flow.Templating.SandboxedTemplateContext 100% 100%
Kagaku.Flow.Templating.TemplateError 100%
Kagaku.Flow.Templating.TemplateHost 100% 100%
Kagaku.Flow.Templating.TemplateLimits 100%
Kagaku.Flow.Templating.TemplateModel 100% 100%
Kagaku.Flow.Templating.TemplateProblem 100%
Kagaku.Flow.Templating.TemplateRenderRequest 100% 100%
Kagaku.Flow.Templating.TemplateRenderResult 100%
Kagaku.Flow.Templating.TemplateRun 100%
Kagaku.Flow.Templating.TemplateSinks 100%
Kagaku.Flow.Templating.TemplateValues 100% 100%
Kagaku.Flow.Validation.GraphValidation 100% 100%
Kagaku.Flow.Validation.GraphValidationContext 100%
Kagaku.Flow.Validation.HookFailure 100%
Kagaku.Flow.Validation.ResolvedGraph 100% 100%
Kagaku.Flow.Validation.ValidationError 100%
Kagaku.Flow.Validation.ValidationReport 100%
System.Text.RegularExpressions.Generated 97.4% 92.8%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F6329A328226B0CF
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__ColorToken_2
97.4% 94.4%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F6329A328226B0CF
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__Socket_1
97.2% 94.4%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F6329A328226B0CF
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__TypeId_0
97.2% 94.4%
Kagaku.Flow.OpenRouter - 100%
Name Line Branch
Kagaku.Flow.OpenRouter 100% 100%
Kagaku.Flow.OpenRouter.Catalog.ModelCapabilities 100% 100%
Kagaku.Flow.OpenRouter.Gateway.OpenRouterGateway 100% 100%
Kagaku.Flow.OpenRouter.Gateway.OpenRouterGatewayOptions 100%
Kagaku.Flow.OpenRouter.Gateway.OpenRouterKeyLookup 100% 100%
Kagaku.Flow.OpenRouter.OpenRouterServiceCollectionExtensions 100% 100%
Kagaku.Flow.Testing - 100%
Name Line Branch
Kagaku.Flow.Testing 100% 100%
Kagaku.Flow.Testing.FlowEngineDriver 100% 100%
Kagaku.Flow.Testing.InMemoryRunLedger 100% 100%
Kagaku.Flow.Testing.RecordingRunEventSink 100%
Kagaku.Flow.Testing.RunLedgerContract 100% 100%
Kagaku.Flow.Testing.RunLedgerContractException 100%
Tsumugi.BlazorAdapter - 96.3%
Name Line Branch
Tsumugi.BlazorAdapter 96.3% 92.3%
Tsumugi.BlazorAdapter.BlazorAdapterAssembly 100%
Tsumugi.BlazorAdapter.Documents.CategoryCreated 100%
Tsumugi.BlazorAdapter.Documents.CategoryEditor 91.8% 87.2%
Tsumugi.BlazorAdapter.Documents.CategorySaved 100%
Tsumugi.BlazorAdapter.Documents.ChangeCategoryFieldsRequested 100%
Tsumugi.BlazorAdapter.Documents.CreateCategoryRequested 100%
Tsumugi.BlazorAdapter.Documents.CreateDocumentRequested 100%
Tsumugi.BlazorAdapter.Documents.DeleteCategoryChoice 100%
Tsumugi.BlazorAdapter.Documents.DeleteCategoryDialog 100% 94.1%
Tsumugi.BlazorAdapter.Documents.DeleteCategoryRequested 100%
Tsumugi.BlazorAdapter.Documents.DeleteDocumentRequested 100%
Tsumugi.BlazorAdapter.Documents.DocumentAutoSaveFailed 100%
Tsumugi.BlazorAdapter.Documents.DocumentCreated 100%
Tsumugi.BlazorAdapter.Documents.DocumentEditor 94.8% 88%
Tsumugi.BlazorAdapter.Documents.DocumentsActionFailed 100%
Tsumugi.BlazorAdapter.Documents.DocumentSaved 100%
Tsumugi.BlazorAdapter.Documents.DocumentsEffects 100% 100%
Tsumugi.BlazorAdapter.Documents.DocumentsList 98.2% 92.4%
Tsumugi.BlazorAdapter.Documents.DocumentsLoaded 100%
Tsumugi.BlazorAdapter.Documents.DocumentsPage 100% 100%
Tsumugi.BlazorAdapter.Documents.DocumentsReducers 100% 100%
Tsumugi.BlazorAdapter.Documents.DocumentsRefusal 100%
Tsumugi.BlazorAdapter.Documents.DocumentsState 100% 100%
Tsumugi.BlazorAdapter.Documents.FieldInput 100% 92.3%
Tsumugi.BlazorAdapter.Documents.FieldsChanges 100% 100%
Tsumugi.BlazorAdapter.Documents.FieldShapes 88.8% 85.7%
Tsumugi.BlazorAdapter.Documents.ImpactText 100% 92.3%
Tsumugi.BlazorAdapter.Documents.LoadDocuments 100%
Tsumugi.BlazorAdapter.Documents.MoveDocumentChoice 100%
Tsumugi.BlazorAdapter.Documents.MoveDocumentDialog 95.6% 71.4%
Tsumugi.BlazorAdapter.Documents.MoveDocumentsRequested 100%
Tsumugi.BlazorAdapter.Documents.ReorderCategoriesRequested 100%
Tsumugi.BlazorAdapter.Documents.SetCategoryCollapsed 100%
Tsumugi.BlazorAdapter.Documents.SetDocumentsFilter 100%
Tsumugi.BlazorAdapter.Documents.UpdateCategoryRequested 100%
Tsumugi.BlazorAdapter.Documents.UpdateDocumentRequested 100%
Tsumugi.BlazorAdapter.Graphs.GraphCanvas 37.5% 25%
Tsumugi.BlazorAdapter.Graphs.GraphPlaygroundPage 20% 25%
Tsumugi.BlazorAdapter.Notifications.DomainChangedBridge 94.4% 83.3%
Tsumugi.BlazorAdapter.Notifications.DomainChangesReceived 75% 100%
Tsumugi.BlazorAdapter.Runs.RunChangedBridge 97.1% 90.6%
Tsumugi.BlazorAdapter.Runs.RunChangesReceived 100%
Tsumugi.BlazorAdapter.Runs.RunTextReceived 100%
Tsumugi.BlazorAdapter.Settings.BackupCompleted 100%
Tsumugi.BlazorAdapter.Settings.BackupFailed 100%
Tsumugi.BlazorAdapter.Settings.BackupNowRequested 100%
Tsumugi.BlazorAdapter.Settings.BackupSettingsLoaded 100%
Tsumugi.BlazorAdapter.Settings.BackupsSection 100% 100%
Tsumugi.BlazorAdapter.Settings.BackupsSettingsEffects 100% 100%
Tsumugi.BlazorAdapter.Settings.BackupsSettingsReducers 100%
Tsumugi.BlazorAdapter.Settings.BackupsSettingsState 100%
Tsumugi.BlazorAdapter.Settings.DefaultsSection 100% 100%
Tsumugi.BlazorAdapter.Settings.DefaultsSettingsEffects 100% 100%
Tsumugi.BlazorAdapter.Settings.DefaultsSettingsReducers 100%
Tsumugi.BlazorAdapter.Settings.DefaultsSettingsState 100%
Tsumugi.BlazorAdapter.Settings.LlmDefaultsLoaded 100%
Tsumugi.BlazorAdapter.Settings.LlmDefaultsSaved 100%
Tsumugi.BlazorAdapter.Settings.LlmDefaultsSaveFailed 100%
Tsumugi.BlazorAdapter.Settings.LoadBackupSettings 100%
Tsumugi.BlazorAdapter.Settings.LoadProviderSettings 100%
Tsumugi.BlazorAdapter.Settings.ModelCatalogLoaded 100%
Tsumugi.BlazorAdapter.Settings.ModelCatalogLoadFailed 100%
Tsumugi.BlazorAdapter.Settings.OpenRouterKeySaved 100%
Tsumugi.BlazorAdapter.Settings.OpenRouterKeySaveFailed 100%
Tsumugi.BlazorAdapter.Settings.ProviderKeyChecked 100%
Tsumugi.BlazorAdapter.Settings.ProviderSection 100% 100%
Tsumugi.BlazorAdapter.Settings.ProviderSettingsEffects 100% 100%
Tsumugi.BlazorAdapter.Settings.ProviderSettingsLoaded 100%
Tsumugi.BlazorAdapter.Settings.ProviderSettingsReducers 100% 100%
Tsumugi.BlazorAdapter.Settings.ProviderSettingsState 100% 100%
Tsumugi.BlazorAdapter.Settings.SaveLlmDefaultsRequested 100%
Tsumugi.BlazorAdapter.Settings.SaveOpenRouterKeyRequested 100%
Tsumugi.BlazorAdapter.Stories.CreateStoryRequested 100%
Tsumugi.BlazorAdapter.Stories.PurgeStoryRequested 100%
Tsumugi.BlazorAdapter.Stories.RestoreStoryRequested 100%
Tsumugi.BlazorAdapter.Stories.SetShowArchived 100%
Tsumugi.BlazorAdapter.Stories.SetStoriesFilter 100%
Tsumugi.BlazorAdapter.Stories.SetStoryArchivedRequested 100%
Tsumugi.BlazorAdapter.Stories.StoriesEffects 100% 100%
Tsumugi.BlazorAdapter.Stories.StoriesLoaded 100%
Tsumugi.BlazorAdapter.Stories.StoriesPage 100% 100%
Tsumugi.BlazorAdapter.Stories.StoriesReducers 100% 100%
Tsumugi.BlazorAdapter.Stories.StoriesState 100% 100%
Tsumugi.BlazorAdapter.Stories.StoryActionFailed 100%
Tsumugi.BlazorAdapter.Stories.StoryCreated 100%
Tsumugi.BlazorAdapter.Stories.StoryCreateFailed 100%
Tsumugi.BlazorAdapter.Stories.StoryTrashPage 100% 100%
Tsumugi.BlazorAdapter.Stories.TrashStoryRequested 100%
Tsumugi.BlazorAdapter.Workspace.AutoSaveFailed 100%
Tsumugi.BlazorAdapter.Workspace.CaretRequest 100%
Tsumugi.BlazorAdapter.Workspace.ChapterHeader 100% 86.3%
Tsumugi.BlazorAdapter.Workspace.ChapterMenu 100% 81.2%
Tsumugi.BlazorAdapter.Workspace.ChapterPage 100% 87.5%
Tsumugi.BlazorAdapter.Workspace.ChapterSaved 100%
Tsumugi.BlazorAdapter.Workspace.ChaptersPage 90% 83.3%
Tsumugi.BlazorAdapter.Workspace.CreateChapterRequested 100%
Tsumugi.BlazorAdapter.Workspace.DeleteChapterRequested 100%
Tsumugi.BlazorAdapter.Workspace.DeleteSegmentRequested 100%
Tsumugi.BlazorAdapter.Workspace.EditSegmentRequested 100%
Tsumugi.BlazorAdapter.Workspace.HistoryEffects 100% 100%
Tsumugi.BlazorAdapter.Workspace.HistoryLoaded 100%
Tsumugi.BlazorAdapter.Workspace.HistoryPage 100%
Tsumugi.BlazorAdapter.Workspace.HistoryPanel 100% 100%
Tsumugi.BlazorAdapter.Workspace.InsertSegmentRequested 100%
Tsumugi.BlazorAdapter.Workspace.LoadHistory 100%
Tsumugi.BlazorAdapter.Workspace.LoadWorkspace 100%
Tsumugi.BlazorAdapter.Workspace.ManuscriptColumn 94.9% 89.4%
Tsumugi.BlazorAdapter.Workspace.ManuscriptEffects 100% 100%
Tsumugi.BlazorAdapter.Workspace.MergeChapterRequested 100%
Tsumugi.BlazorAdapter.Workspace.MergeSegmentRequested 100%
Tsumugi.BlazorAdapter.Workspace.MoveChapterRequested 100%
Tsumugi.BlazorAdapter.Workspace.RedoRequested 100%
Tsumugi.BlazorAdapter.Workspace.RedoRevertRequested 100%
Tsumugi.BlazorAdapter.Workspace.RenameChapterRequested 100%
Tsumugi.BlazorAdapter.Workspace.RevertToSnapshotRequested 100%
Tsumugi.BlazorAdapter.Workspace.SaveScope 100% 50%
Tsumugi.BlazorAdapter.Workspace.SaveStateLadder 100% 100%
Tsumugi.BlazorAdapter.Workspace.SaveVersionRequested 100%
Tsumugi.BlazorAdapter.Workspace.SegmentRow 100% 96.8%
Tsumugi.BlazorAdapter.Workspace.SegmentSaved 100%
Tsumugi.BlazorAdapter.Workspace.SetChapterSummaryRequested 100%
Tsumugi.BlazorAdapter.Workspace.SplitSegmentRequested 100%
Tsumugi.BlazorAdapter.Workspace.StoryDetails 100% 85.2%
Tsumugi.BlazorAdapter.Workspace.StoryGeneralPage 100% 100%
Tsumugi.BlazorAdapter.Workspace.StorySaved 100%
Tsumugi.BlazorAdapter.Workspace.StoryShell 100% 100%
Tsumugi.BlazorAdapter.Workspace.UndoRequested 100%
Tsumugi.BlazorAdapter.Workspace.UndoStatusChanged 100%
Tsumugi.BlazorAdapter.Workspace.UpdateStoryRequested 100%
Tsumugi.BlazorAdapter.Workspace.WorkspaceActionFailed 100%
Tsumugi.BlazorAdapter.Workspace.WorkspaceLoaded 100%
Tsumugi.BlazorAdapter.Workspace.WorkspaceNotFound 100%
Tsumugi.BlazorAdapter.Workspace.WorkspaceReducers 100% 100%
Tsumugi.BlazorAdapter.Workspace.WorkspaceState 100%
Tsumugi.Domain - 95.5%
Name Line Branch
Tsumugi.Domain 95.5% 93.5%
System.Text.RegularExpressions.Generated 90.7% 87.5%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F5BB471BFAD87E25
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__DigitsAndSeparators_2
97.4% 95.8%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F5BB471BFAD87E25
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__LineBreak_0
91% 91.1%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F5BB471BFAD87E25
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__NumberToken_1
86.4% 77.7%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F5BB471BFAD87E25
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__Plain_4
91.9% 88.8%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F5BB471BFAD87E25
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__ThousandsGroup_3
90.9% 84.3%
Tsumugi.Domain.Documents.CategoryFields 94.5% 93.7%
Tsumugi.Domain.Documents.ChoiceOption 100%
Tsumugi.Domain.Documents.ChoiceValue 100%
Tsumugi.Domain.Documents.DateText 100%
Tsumugi.Domain.Documents.DateValue 100%
Tsumugi.Domain.Documents.Document 100% 100%
Tsumugi.Domain.Documents.DocumentCategory 100% 100%
Tsumugi.Domain.Documents.DocumentValues 100%
Tsumugi.Domain.Documents.FieldCarry 100% 100%
Tsumugi.Domain.Documents.FieldConversion 100% 100%
Tsumugi.Domain.Documents.FieldDefinition 96.9% 95%
Tsumugi.Domain.Documents.FieldMessages 100% 100%
Tsumugi.Domain.Documents.FieldNames 100%
Tsumugi.Domain.Documents.FieldsChange 100%
Tsumugi.Domain.Documents.FieldShape 94.7% 85.7%
Tsumugi.Domain.Documents.FieldsPlan 100%
Tsumugi.Domain.Documents.FieldsPlanner 99.2% 98.4%
Tsumugi.Domain.Documents.FieldValue 100%
Tsumugi.Domain.Documents.FieldValues 95.4% 97.7%
Tsumugi.Domain.Documents.MovePlan 100%
Tsumugi.Domain.Documents.NumberParse 100%
Tsumugi.Domain.Documents.NumberText 100% 97.6%
Tsumugi.Domain.Documents.NumberValue 100%
Tsumugi.Domain.Documents.TextValue 100% 100%
Tsumugi.Domain.Documents.ValueChange 100%
Tsumugi.Domain.Documents.ValueChangeKey 100%
Tsumugi.Domain.Documents.ValueImpact 100% 100%
Tsumugi.Domain.Journal.ChangeLogEntry 97.8% 75%
Tsumugi.Domain.Journal.StorySnapshot 95.4% 83.3%
Tsumugi.Domain.Settings.AppSetting 100%
Tsumugi.Domain.Stories.Chapter 100% 100%
Tsumugi.Domain.Stories.Provenance 100% 100%
Tsumugi.Domain.Stories.Segment 100% 100%
Tsumugi.Domain.Stories.Story 100% 100%
Tsumugi.Domain.Text 100% 100%
Tsumugi.Infrastructure - 97.4%
Name Line Branch
Tsumugi.Infrastructure 97.4% 85.8%
System.Text.RegularExpressions.Generated 81.4% 64.8%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F0D076C66E8EEC1C
A55E0373ED22DC58420DBB40F103A46B68DC4D9CE5A49E00B__Pattern_0
84% 65.7%
Tsumugi.Infrastructure.Backups.BackupArtifactName 100% 100%
Tsumugi.Infrastructure.Backups.BackupOptions 100%
Tsumugi.Infrastructure.Backups.BackupSchedule 100% 83.3%
Tsumugi.Infrastructure.Backups.BackupScheduler 96.5% 83.3%
Tsumugi.Infrastructure.Backups.BackupWriter 98.5% 92.5%
Tsumugi.Infrastructure.Backups.DatabaseFingerprint 91.9% 97%
Tsumugi.Infrastructure.Backups.FileSystemBackupStore 100% 100%
Tsumugi.Infrastructure.Backups.PreMigrationBackup 89.1% 100%
Tsumugi.Infrastructure.Backups.SqliteSnapshot 87.8% 75%
Tsumugi.Infrastructure.DependencyInjection 100% 100%
Tsumugi.Infrastructure.Journal.EfChangeJournal 100%
Tsumugi.Infrastructure.Journal.EfSnapshotStore 100%
Tsumugi.Infrastructure.Journal.EfUndoStore 98.1% 85.7%
Tsumugi.Infrastructure.Journal.ModelExtensions 100%
Tsumugi.Infrastructure.Journal.OperationContext 100% 100%
Tsumugi.Infrastructure.Journal.PropertyBag 100% 95.4%
Tsumugi.Infrastructure.Notifications.InProcessDomainChangedBus 100% 100%
Tsumugi.Infrastructure.Persistence.Configurations.AppSettingConfiguration 100%
Tsumugi.Infrastructure.Persistence.Configurations.ChangeLogEntryConfigurati
on
100%
Tsumugi.Infrastructure.Persistence.Configurations.ChapterConfiguration 100%
Tsumugi.Infrastructure.Persistence.Configurations.DocumentCategoryConfigura
tion
100%
Tsumugi.Infrastructure.Persistence.Configurations.DocumentConfiguration 100%
Tsumugi.Infrastructure.Persistence.Configurations.SegmentConfiguration 100%
Tsumugi.Infrastructure.Persistence.Configurations.StoryConfiguration 100%
Tsumugi.Infrastructure.Persistence.Configurations.StorySnapshotConfiguratio
n
100%
Tsumugi.Infrastructure.Persistence.Converters.CanonicalJsonComparer 100%
Tsumugi.Infrastructure.Persistence.Converters.CategoryFieldsConverter 97.5% 93.9%
Tsumugi.Infrastructure.Persistence.Converters.FieldValuesConverter 96.7% 92.8%
Tsumugi.Infrastructure.Persistence.Converters.ProvenanceConverter 100% 90%
Tsumugi.Infrastructure.Persistence.Converters.UtcTicksConverter 100%
Tsumugi.Infrastructure.Persistence.Converters.WireFormat 85.7% 73.8%
Tsumugi.Infrastructure.Persistence.Migrations.AddAppSettings 99.2%
Tsumugi.Infrastructure.Persistence.Migrations.AddJournal 98.8%
Tsumugi.Infrastructure.Persistence.Migrations.CategoryFields 100% 100%
Tsumugi.Infrastructure.Persistence.Migrations.CategoryFieldsSql 100%
Tsumugi.Infrastructure.Persistence.Migrations.DropDocumentBody 100%
Tsumugi.Infrastructure.Persistence.Migrations.GenericDocuments 100%
Tsumugi.Infrastructure.Persistence.Migrations.InitialStoryDomain 97.3%
Tsumugi.Infrastructure.Persistence.Migrations.TsumugiDbContextModelSnapshot 100%
Tsumugi.Infrastructure.Persistence.SqlitePragmaInterceptor 100% 100%
Tsumugi.Infrastructure.Persistence.Stores.EfChapterStore 100% 100%
Tsumugi.Infrastructure.Persistence.Stores.EfDocumentStore 98.2% 50%
Tsumugi.Infrastructure.Persistence.Stores.EfSegmentStore 95% 84.6%
Tsumugi.Infrastructure.Persistence.Stores.EfStoryStore 98.3% 92.8%
Tsumugi.Infrastructure.Persistence.TsumugiDbContext 85.4% 89.2%
Tsumugi.Infrastructure.Persistence.TsumugiDbContextFactory 0%
Tsumugi.Infrastructure.Provider.OpenRouterKeySource 100% 100%
Tsumugi.Infrastructure.Provider.OpenRouterModelProvider 100% 100%
Tsumugi.Infrastructure.Provider.OpenRouterOptions 100% 100%
Tsumugi.Infrastructure.Provider.ResolvedKey 100%
Tsumugi.Infrastructure.Runs.InProcessRunEventBus 100% 100%
Tsumugi.Infrastructure.Settings.EfAppSettingsStore 100% 100%
Tsumugi.Kernel - 90.9%
Name Line Branch
Tsumugi.Kernel 90.9% 75%
Tsumugi.Kernel.Err`1 100%
Tsumugi.Kernel.Ok`1 100%
Tsumugi.Kernel.Result`1 88.8% 75%
Tsumugi.Server - 100%
Name Line Branch
Tsumugi.Server 100% 86.4%
Program 100% 100%
Tsumugi.Server.Backups.BackupConfiguration 100% 100%
Tsumugi.Server.Components.App 100%
Tsumugi.Server.Components.Layout.AppHeader 100% 50%
Tsumugi.Server.Components.Layout.MainLayout 100%
Tsumugi.Server.Components.Pages.Gate 100% 100%
Tsumugi.Server.Provider.OpenRouterConfiguration 100% 100%
Tsumugi.Server.Security.AccessGate 100% 66.6%
Tsumugi.Server.Security.AccessSecret 100% 50%
Tsumugi.Server.VolumeStartupValidator 100% 100%
Tsumugi.UseCases - 98.2%
Name Line Branch
Tsumugi.UseCases 98.2% 93.5%
Tsumugi.UseCases.Backups.BackupArtifact 100%
Tsumugi.UseCases.Backups.BackupPolicy 100%
Tsumugi.UseCases.Backups.BackupStatusView 100% 100%
Tsumugi.UseCases.Backups.DatabaseSnapshot 100%
Tsumugi.UseCases.Backups.DownloadDatabaseSnapshot 100%
Tsumugi.UseCases.Backups.GetBackupStatus 100%
Tsumugi.UseCases.Backups.RunBackupNow 100%
Tsumugi.UseCases.Chapters.ChapterDto 100%
Tsumugi.UseCases.Chapters.CreateChapter 100% 100%
Tsumugi.UseCases.Chapters.DeleteChapter 100% 100%
Tsumugi.UseCases.Chapters.GetManuscript 100% 100%
Tsumugi.UseCases.Chapters.ManuscriptChapter 100%
Tsumugi.UseCases.Chapters.ManuscriptView 100%
Tsumugi.UseCases.Chapters.MergeChapters 100% 100%
Tsumugi.UseCases.Chapters.RenameChapter 100% 100%
Tsumugi.UseCases.Chapters.ReorderChapters 100% 100%
Tsumugi.UseCases.Chapters.SetChapterSummary 100% 75%
Tsumugi.UseCases.DependencyInjection 100%
Tsumugi.UseCases.Diagnostics.SeedDevData 98.1% 71.4%
Tsumugi.UseCases.Documents.ChangeCategoryFields 100% 83.3%
Tsumugi.UseCases.Documents.CreateCategory 100% 100%
Tsumugi.UseCases.Documents.CreateDocument 94.4% 100%
Tsumugi.UseCases.Documents.DefaultFields 100% 100%
Tsumugi.UseCases.Documents.DeleteCategory 100% 94.4%
Tsumugi.UseCases.Documents.DeleteDocument 100% 100%
Tsumugi.UseCases.Documents.DocumentCategoryDto 100%
Tsumugi.UseCases.Documents.DocumentCategoryProfile 100%
Tsumugi.UseCases.Documents.DocumentDto 100%
Tsumugi.UseCases.Documents.DocumentPatch 100%
Tsumugi.UseCases.Documents.DocumentProfile 100%
Tsumugi.UseCases.Documents.DocumentsCategory 100%
Tsumugi.UseCases.Documents.DocumentsView 100%
Tsumugi.UseCases.Documents.DocumentWrites 100% 100%
Tsumugi.UseCases.Documents.FieldsChangeLabel 95.4% 88.2%
Tsumugi.UseCases.Documents.GetDocuments 100% 100%
Tsumugi.UseCases.Documents.ImpactPreview 100% 100%
Tsumugi.UseCases.Documents.LossMessages 92.3% 87.5%
Tsumugi.UseCases.Documents.MoveDocuments 100% 100%
Tsumugi.UseCases.Documents.ReorderCategories 100% 100%
Tsumugi.UseCases.Documents.UpdateCategory 100% 100%
Tsumugi.UseCases.Documents.UpdateDocument 100% 81.2%
Tsumugi.UseCases.Journal.GetStoryHistory 100% 100%
Tsumugi.UseCases.Journal.GetUndoStatus 100% 100%
Tsumugi.UseCases.Journal.HistoryOperation 66.6%
Tsumugi.UseCases.Journal.HistorySnapshot 87.5%
Tsumugi.UseCases.Journal.OperationGrouping 100% 100%
Tsumugi.UseCases.Journal.RedoRevert 100% 100%
Tsumugi.UseCases.Journal.RedoRun 75% 57.1%
Tsumugi.UseCases.Journal.RedoTyping 100% 100%
Tsumugi.UseCases.Journal.RevertConflict 100%
Tsumugi.UseCases.Journal.RevertOutcome 100%
Tsumugi.UseCases.Journal.RevertRun 96.5% 77.7%
Tsumugi.UseCases.Journal.RevertToSnapshot 92.3% 83.3%
Tsumugi.UseCases.Journal.StoryHistory 100%
Tsumugi.UseCases.Journal.TakeSnapshot 100% 87.5%
Tsumugi.UseCases.Journal.UndoStatus 100%
Tsumugi.UseCases.Journal.UndoTyping 100% 100%
Tsumugi.UseCases.Journal.UserOperation 100%
Tsumugi.UseCases.Notifications.DomainChanged 100%
Tsumugi.UseCases.Ordering 100% 100%
Tsumugi.UseCases.Runs.RunEvent 100%
Tsumugi.UseCases.Segments.DeleteSegments 100% 100%
Tsumugi.UseCases.Segments.EditSegment 100% 83.3%
Tsumugi.UseCases.Segments.InsertSegment 100% 100%
Tsumugi.UseCases.Segments.MergeSegmentWithPrevious 100% 100%
Tsumugi.UseCases.Segments.MoveSegments 100% 100%
Tsumugi.UseCases.Segments.ReorderSegments 100% 100%
Tsumugi.UseCases.Segments.SegmentDto 100%
Tsumugi.UseCases.Segments.SplitSegment 100% 100%
Tsumugi.UseCases.Settings.CatalogModel 100% 100%
Tsumugi.UseCases.Settings.CheckOpenRouterKey 100%
Tsumugi.UseCases.Settings.GetLlmDefaults 100% 100%
Tsumugi.UseCases.Settings.GetModelCatalog 100% 100%
Tsumugi.UseCases.Settings.GetProviderSettings 100%
Tsumugi.UseCases.Settings.LlmDefaults 100%
Tsumugi.UseCases.Settings.ProviderKeyInfo 100%
Tsumugi.UseCases.Settings.ProviderKeyState 100%
Tsumugi.UseCases.Settings.ProviderSettingsView 100% 100%
Tsumugi.UseCases.Settings.ReasoningEfforts 100% 100%
Tsumugi.UseCases.Settings.RemoveOpenRouterKey 100%
Tsumugi.UseCases.Settings.RoutingDefaults 100%
Tsumugi.UseCases.Settings.RoutingSettings 100% 100%
Tsumugi.UseCases.Settings.SaveLlmDefaults 100% 100%
Tsumugi.UseCases.Settings.SaveOpenRouterKey 100% 100%
Tsumugi.UseCases.Stories.CreateStory 100% 100%
Tsumugi.UseCases.Stories.GetStory 100% 100%
Tsumugi.UseCases.Stories.ListStories 100% 75%
Tsumugi.UseCases.Stories.PurgeStory 100% 100%
Tsumugi.UseCases.Stories.RestoreStory 100% 100%
Tsumugi.UseCases.Stories.SetActiveGraph 100% 100%
Tsumugi.UseCases.Stories.SetStoryArchived 100% 100%
Tsumugi.UseCases.Stories.StarterCategories 100%
Tsumugi.UseCases.Stories.StoryDetail 100%
Tsumugi.UseCases.Stories.StoryListItem 81.8%
Tsumugi.UseCases.Stories.StoryProfile 100%
Tsumugi.UseCases.Stories.TrashStory 100% 100%
Tsumugi.UseCases.Stories.UpdateStory 100% 100%
<!-- coverage-comment --> > 🔄 **Auto-updating coverage report** — this comment is regenerated on every push, so the numbers below always reflect the commit shown here, not the branch tip. > > **Commit:** `47e9912` · **Generated:** 2026-09-25 21:32:35 UTC · **Revision:** #1 # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 09/25/2026 - 21:32:35 | | Coverage date: | 09/25/2026 - 21:32:12 - 09/25/2026 - 21:32:32 | | Parser: | MultiReport (6x Cobertura) | | Assemblies: | 9 | | Classes: | 422 | | Files: | 403 | | **Line coverage:** | 97.6% (12170 of 12465) | | Covered lines: | 12170 | | Uncovered lines: | 295 | | Coverable lines: | 12465 | | Total lines: | 27136 | | **Branch coverage:** | 93.2% (4518 of 4845) | | Covered branches: | 4518 | | Total branches: | 4845 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>Kagaku.Flow - 99.7%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Kagaku.Flow**|**99.7%**|**99.2%**| |Kagaku.Flow.Budgeting.BudgetRequest|100%|| |Kagaku.Flow.Budgeting.ConsumerBudget|100%|| |Kagaku.Flow.Execution.NodeExecution|100%|100%| |Kagaku.Flow.Execution.NodeOutcome|100%|100%| |Kagaku.Flow.Execution.TransientFailureException|100%|| |Kagaku.Flow.Execution.Upstream|100%|| |Kagaku.Flow.Extensions.ServiceCollectionExtensions|100%|| |Kagaku.Flow.FlowJson|100%|| |Kagaku.Flow.Graphs.GraphDefinition|100%|| |Kagaku.Flow.Graphs.GraphDocument|100%|100%| |Kagaku.Flow.Graphs.GraphEdge|100%|| |Kagaku.Flow.Graphs.GraphIndex|100%|100%| |Kagaku.Flow.Graphs.GraphLoadError|100%|| |Kagaku.Flow.Graphs.GraphLoadResult|100%|| |Kagaku.Flow.Graphs.GraphNode|100%|| |Kagaku.Flow.Graphs.GraphPosition|100%|| |Kagaku.Flow.Graphs.GraphReader|100%|100%| |Kagaku.Flow.Graphs.GraphSchema|100%|100%| |Kagaku.Flow.Graphs.GraphShapeException|100%|| |Kagaku.Flow.Graphs.GraphWriter|100%|100%| |Kagaku.Flow.Graphs.SocketRef|100%|| |Kagaku.Flow.Graphs.WireText|100%|100%| |Kagaku.Flow.Nodes.CanvasDescriptorWriter|100%|100%| |Kagaku.Flow.Nodes.ConfigProblem|100%|| |Kagaku.Flow.Nodes.Names|100%|| |Kagaku.Flow.Nodes.NodeDescriptor|100%|100%| |Kagaku.Flow.Nodes.NodeRegistry|100%|100%| |Kagaku.Flow.Nodes.NodeSockets|100%|100%| |Kagaku.Flow.Nodes.SinkDescriptor|100%|| |Kagaku.Flow.Nodes.SocketTypes|100%|100%| |Kagaku.Flow.Nodes.SourceDescriptor|100%|| |Kagaku.Flow.Nodes.TemplateField|100%|| |Kagaku.Flow.Runs.LedgerChange|100%|100%| |Kagaku.Flow.Runs.LedgerTransitions|100%|100%| |Kagaku.Flow.Runs.NodeAttempt|100%|| |Kagaku.Flow.Runs.NodeFacts|100%|| |Kagaku.Flow.Runs.NodeRun|100%|| |Kagaku.Flow.Runs.Run|100%|100%| |Kagaku.Flow.Runs.RunEvent|100%|| |Kagaku.Flow.Runs.RunStatuses|100%|100%| |Kagaku.Flow.Runs.RunTrigger|100%|100%| |Kagaku.Flow.Scheduling.Exclusion|100%|| |Kagaku.Flow.Scheduling.FlowEngine|100%|100%| |Kagaku.Flow.Scheduling.FlowEngineOptions|100%|100%| |Kagaku.Flow.Scheduling.ReadyRow|100%|| |Kagaku.Flow.Scheduling.ReadySet|100%|100%| |Kagaku.Flow.Scheduling.ReadySetResult|100%|| |Kagaku.Flow.Scheduling.RunRequest|100%|100%| |Kagaku.Flow.Scheduling.RunStartResult|100%|| |Kagaku.Flow.Scheduling.SocketValues|100%|100%| |Kagaku.Flow.Templating.InputsWalker|100%|100%| |Kagaku.Flow.Templating.ParsedTemplate|100%|100%| |Kagaku.Flow.Templating.SandboxedTemplateContext|100%|100%| |Kagaku.Flow.Templating.TemplateError|100%|| |Kagaku.Flow.Templating.TemplateHost|100%|100%| |Kagaku.Flow.Templating.TemplateLimits|100%|| |Kagaku.Flow.Templating.TemplateModel|100%|100%| |Kagaku.Flow.Templating.TemplateProblem|100%|| |Kagaku.Flow.Templating.TemplateRenderRequest|100%|100%| |Kagaku.Flow.Templating.TemplateRenderResult|100%|| |Kagaku.Flow.Templating.TemplateRun|100%|| |Kagaku.Flow.Templating.TemplateSinks|100%|| |Kagaku.Flow.Templating.TemplateValues|100%|100%| |Kagaku.Flow.Validation.GraphValidation|100%|100%| |Kagaku.Flow.Validation.GraphValidationContext|100%|| |Kagaku.Flow.Validation.HookFailure|100%|| |Kagaku.Flow.Validation.ResolvedGraph|100%|100%| |Kagaku.Flow.Validation.ValidationError|100%|| |Kagaku.Flow.Validation.ValidationReport|100%|| |System.Text.RegularExpressions.Generated|97.4%|92.8%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F6329A328226B0CF<br/>E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__ColorToken_2|97.4%|94.4%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F6329A328226B0CF<br/>E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__Socket_1|97.2%|94.4%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F6329A328226B0CF<br/>E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__TypeId_0|97.2%|94.4%| </details> <details><summary>Kagaku.Flow.OpenRouter - 100%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Kagaku.Flow.OpenRouter**|**100%**|**100%**| |Kagaku.Flow.OpenRouter.Catalog.ModelCapabilities|100%|100%| |Kagaku.Flow.OpenRouter.Gateway.OpenRouterGateway|100%|100%| |Kagaku.Flow.OpenRouter.Gateway.OpenRouterGatewayOptions|100%|| |Kagaku.Flow.OpenRouter.Gateway.OpenRouterKeyLookup|100%|100%| |Kagaku.Flow.OpenRouter.OpenRouterServiceCollectionExtensions|100%|100%| </details> <details><summary>Kagaku.Flow.Testing - 100%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Kagaku.Flow.Testing**|**100%**|**100%**| |Kagaku.Flow.Testing.FlowEngineDriver|100%|100%| |Kagaku.Flow.Testing.InMemoryRunLedger|100%|100%| |Kagaku.Flow.Testing.RecordingRunEventSink|100%|| |Kagaku.Flow.Testing.RunLedgerContract|100%|100%| |Kagaku.Flow.Testing.RunLedgerContractException|100%|| </details> <details><summary>Tsumugi.BlazorAdapter - 96.3%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Tsumugi.BlazorAdapter**|**96.3%**|**92.3%**| |Tsumugi.BlazorAdapter.BlazorAdapterAssembly|100%|| |Tsumugi.BlazorAdapter.Documents.CategoryCreated|100%|| |Tsumugi.BlazorAdapter.Documents.CategoryEditor|91.8%|87.2%| |Tsumugi.BlazorAdapter.Documents.CategorySaved|100%|| |Tsumugi.BlazorAdapter.Documents.ChangeCategoryFieldsRequested|100%|| |Tsumugi.BlazorAdapter.Documents.CreateCategoryRequested|100%|| |Tsumugi.BlazorAdapter.Documents.CreateDocumentRequested|100%|| |Tsumugi.BlazorAdapter.Documents.DeleteCategoryChoice|100%|| |Tsumugi.BlazorAdapter.Documents.DeleteCategoryDialog|100%|94.1%| |Tsumugi.BlazorAdapter.Documents.DeleteCategoryRequested|100%|| |Tsumugi.BlazorAdapter.Documents.DeleteDocumentRequested|100%|| |Tsumugi.BlazorAdapter.Documents.DocumentAutoSaveFailed|100%|| |Tsumugi.BlazorAdapter.Documents.DocumentCreated|100%|| |Tsumugi.BlazorAdapter.Documents.DocumentEditor|94.8%|88%| |Tsumugi.BlazorAdapter.Documents.DocumentsActionFailed|100%|| |Tsumugi.BlazorAdapter.Documents.DocumentSaved|100%|| |Tsumugi.BlazorAdapter.Documents.DocumentsEffects|100%|100%| |Tsumugi.BlazorAdapter.Documents.DocumentsList|98.2%|92.4%| |Tsumugi.BlazorAdapter.Documents.DocumentsLoaded|100%|| |Tsumugi.BlazorAdapter.Documents.DocumentsPage|100%|100%| |Tsumugi.BlazorAdapter.Documents.DocumentsReducers|100%|100%| |Tsumugi.BlazorAdapter.Documents.DocumentsRefusal|100%|| |Tsumugi.BlazorAdapter.Documents.DocumentsState|100%|100%| |Tsumugi.BlazorAdapter.Documents.FieldInput|100%|92.3%| |Tsumugi.BlazorAdapter.Documents.FieldsChanges|100%|100%| |Tsumugi.BlazorAdapter.Documents.FieldShapes|88.8%|85.7%| |Tsumugi.BlazorAdapter.Documents.ImpactText|100%|92.3%| |Tsumugi.BlazorAdapter.Documents.LoadDocuments|100%|| |Tsumugi.BlazorAdapter.Documents.MoveDocumentChoice|100%|| |Tsumugi.BlazorAdapter.Documents.MoveDocumentDialog|95.6%|71.4%| |Tsumugi.BlazorAdapter.Documents.MoveDocumentsRequested|100%|| |Tsumugi.BlazorAdapter.Documents.ReorderCategoriesRequested|100%|| |Tsumugi.BlazorAdapter.Documents.SetCategoryCollapsed|100%|| |Tsumugi.BlazorAdapter.Documents.SetDocumentsFilter|100%|| |Tsumugi.BlazorAdapter.Documents.UpdateCategoryRequested|100%|| |Tsumugi.BlazorAdapter.Documents.UpdateDocumentRequested|100%|| |Tsumugi.BlazorAdapter.Graphs.GraphCanvas|37.5%|25%| |Tsumugi.BlazorAdapter.Graphs.GraphPlaygroundPage|20%|25%| |Tsumugi.BlazorAdapter.Notifications.DomainChangedBridge|94.4%|83.3%| |Tsumugi.BlazorAdapter.Notifications.DomainChangesReceived|75%|100%| |Tsumugi.BlazorAdapter.Runs.RunChangedBridge|97.1%|90.6%| |Tsumugi.BlazorAdapter.Runs.RunChangesReceived|100%|| |Tsumugi.BlazorAdapter.Runs.RunTextReceived|100%|| |Tsumugi.BlazorAdapter.Settings.BackupCompleted|100%|| |Tsumugi.BlazorAdapter.Settings.BackupFailed|100%|| |Tsumugi.BlazorAdapter.Settings.BackupNowRequested|100%|| |Tsumugi.BlazorAdapter.Settings.BackupSettingsLoaded|100%|| |Tsumugi.BlazorAdapter.Settings.BackupsSection|100%|100%| |Tsumugi.BlazorAdapter.Settings.BackupsSettingsEffects|100%|100%| |Tsumugi.BlazorAdapter.Settings.BackupsSettingsReducers|100%|| |Tsumugi.BlazorAdapter.Settings.BackupsSettingsState|100%|| |Tsumugi.BlazorAdapter.Settings.DefaultsSection|100%|100%| |Tsumugi.BlazorAdapter.Settings.DefaultsSettingsEffects|100%|100%| |Tsumugi.BlazorAdapter.Settings.DefaultsSettingsReducers|100%|| |Tsumugi.BlazorAdapter.Settings.DefaultsSettingsState|100%|| |Tsumugi.BlazorAdapter.Settings.LlmDefaultsLoaded|100%|| |Tsumugi.BlazorAdapter.Settings.LlmDefaultsSaved|100%|| |Tsumugi.BlazorAdapter.Settings.LlmDefaultsSaveFailed|100%|| |Tsumugi.BlazorAdapter.Settings.LoadBackupSettings|100%|| |Tsumugi.BlazorAdapter.Settings.LoadProviderSettings|100%|| |Tsumugi.BlazorAdapter.Settings.ModelCatalogLoaded|100%|| |Tsumugi.BlazorAdapter.Settings.ModelCatalogLoadFailed|100%|| |Tsumugi.BlazorAdapter.Settings.OpenRouterKeySaved|100%|| |Tsumugi.BlazorAdapter.Settings.OpenRouterKeySaveFailed|100%|| |Tsumugi.BlazorAdapter.Settings.ProviderKeyChecked|100%|| |Tsumugi.BlazorAdapter.Settings.ProviderSection|100%|100%| |Tsumugi.BlazorAdapter.Settings.ProviderSettingsEffects|100%|100%| |Tsumugi.BlazorAdapter.Settings.ProviderSettingsLoaded|100%|| |Tsumugi.BlazorAdapter.Settings.ProviderSettingsReducers|100%|100%| |Tsumugi.BlazorAdapter.Settings.ProviderSettingsState|100%|100%| |Tsumugi.BlazorAdapter.Settings.SaveLlmDefaultsRequested|100%|| |Tsumugi.BlazorAdapter.Settings.SaveOpenRouterKeyRequested|100%|| |Tsumugi.BlazorAdapter.Stories.CreateStoryRequested|100%|| |Tsumugi.BlazorAdapter.Stories.PurgeStoryRequested|100%|| |Tsumugi.BlazorAdapter.Stories.RestoreStoryRequested|100%|| |Tsumugi.BlazorAdapter.Stories.SetShowArchived|100%|| |Tsumugi.BlazorAdapter.Stories.SetStoriesFilter|100%|| |Tsumugi.BlazorAdapter.Stories.SetStoryArchivedRequested|100%|| |Tsumugi.BlazorAdapter.Stories.StoriesEffects|100%|100%| |Tsumugi.BlazorAdapter.Stories.StoriesLoaded|100%|| |Tsumugi.BlazorAdapter.Stories.StoriesPage|100%|100%| |Tsumugi.BlazorAdapter.Stories.StoriesReducers|100%|100%| |Tsumugi.BlazorAdapter.Stories.StoriesState|100%|100%| |Tsumugi.BlazorAdapter.Stories.StoryActionFailed|100%|| |Tsumugi.BlazorAdapter.Stories.StoryCreated|100%|| |Tsumugi.BlazorAdapter.Stories.StoryCreateFailed|100%|| |Tsumugi.BlazorAdapter.Stories.StoryTrashPage|100%|100%| |Tsumugi.BlazorAdapter.Stories.TrashStoryRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.AutoSaveFailed|100%|| |Tsumugi.BlazorAdapter.Workspace.CaretRequest|100%|| |Tsumugi.BlazorAdapter.Workspace.ChapterHeader|100%|86.3%| |Tsumugi.BlazorAdapter.Workspace.ChapterMenu|100%|81.2%| |Tsumugi.BlazorAdapter.Workspace.ChapterPage|100%|87.5%| |Tsumugi.BlazorAdapter.Workspace.ChapterSaved|100%|| |Tsumugi.BlazorAdapter.Workspace.ChaptersPage|90%|83.3%| |Tsumugi.BlazorAdapter.Workspace.CreateChapterRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.DeleteChapterRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.DeleteSegmentRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.EditSegmentRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.HistoryEffects|100%|100%| |Tsumugi.BlazorAdapter.Workspace.HistoryLoaded|100%|| |Tsumugi.BlazorAdapter.Workspace.HistoryPage|100%|| |Tsumugi.BlazorAdapter.Workspace.HistoryPanel|100%|100%| |Tsumugi.BlazorAdapter.Workspace.InsertSegmentRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.LoadHistory|100%|| |Tsumugi.BlazorAdapter.Workspace.LoadWorkspace|100%|| |Tsumugi.BlazorAdapter.Workspace.ManuscriptColumn|94.9%|89.4%| |Tsumugi.BlazorAdapter.Workspace.ManuscriptEffects|100%|100%| |Tsumugi.BlazorAdapter.Workspace.MergeChapterRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.MergeSegmentRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.MoveChapterRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.RedoRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.RedoRevertRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.RenameChapterRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.RevertToSnapshotRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.SaveScope|100%|50%| |Tsumugi.BlazorAdapter.Workspace.SaveStateLadder|100%|100%| |Tsumugi.BlazorAdapter.Workspace.SaveVersionRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.SegmentRow|100%|96.8%| |Tsumugi.BlazorAdapter.Workspace.SegmentSaved|100%|| |Tsumugi.BlazorAdapter.Workspace.SetChapterSummaryRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.SplitSegmentRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.StoryDetails|100%|85.2%| |Tsumugi.BlazorAdapter.Workspace.StoryGeneralPage|100%|100%| |Tsumugi.BlazorAdapter.Workspace.StorySaved|100%|| |Tsumugi.BlazorAdapter.Workspace.StoryShell|100%|100%| |Tsumugi.BlazorAdapter.Workspace.UndoRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.UndoStatusChanged|100%|| |Tsumugi.BlazorAdapter.Workspace.UpdateStoryRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.WorkspaceActionFailed|100%|| |Tsumugi.BlazorAdapter.Workspace.WorkspaceLoaded|100%|| |Tsumugi.BlazorAdapter.Workspace.WorkspaceNotFound|100%|| |Tsumugi.BlazorAdapter.Workspace.WorkspaceReducers|100%|100%| |Tsumugi.BlazorAdapter.Workspace.WorkspaceState|100%|| </details> <details><summary>Tsumugi.Domain - 95.5%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Tsumugi.Domain**|**95.5%**|**93.5%**| |System.Text.RegularExpressions.Generated|90.7%|87.5%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F5BB471BFAD87E25<br/>BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__DigitsAndSeparators_2|97.4%|95.8%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F5BB471BFAD87E25<br/>BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__LineBreak_0|91%|91.1%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F5BB471BFAD87E25<br/>BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__NumberToken_1|86.4%|77.7%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F5BB471BFAD87E25<br/>BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__Plain_4|91.9%|88.8%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F5BB471BFAD87E25<br/>BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__ThousandsGroup_3|90.9%|84.3%| |Tsumugi.Domain.Documents.CategoryFields|94.5%|93.7%| |Tsumugi.Domain.Documents.ChoiceOption|100%|| |Tsumugi.Domain.Documents.ChoiceValue|100%|| |Tsumugi.Domain.Documents.DateText|100%|| |Tsumugi.Domain.Documents.DateValue|100%|| |Tsumugi.Domain.Documents.Document|100%|100%| |Tsumugi.Domain.Documents.DocumentCategory|100%|100%| |Tsumugi.Domain.Documents.DocumentValues|100%|| |Tsumugi.Domain.Documents.FieldCarry|100%|100%| |Tsumugi.Domain.Documents.FieldConversion|100%|100%| |Tsumugi.Domain.Documents.FieldDefinition|96.9%|95%| |Tsumugi.Domain.Documents.FieldMessages|100%|100%| |Tsumugi.Domain.Documents.FieldNames|100%|| |Tsumugi.Domain.Documents.FieldsChange|100%|| |Tsumugi.Domain.Documents.FieldShape|94.7%|85.7%| |Tsumugi.Domain.Documents.FieldsPlan|100%|| |Tsumugi.Domain.Documents.FieldsPlanner|99.2%|98.4%| |Tsumugi.Domain.Documents.FieldValue|100%|| |Tsumugi.Domain.Documents.FieldValues|95.4%|97.7%| |Tsumugi.Domain.Documents.MovePlan|100%|| |Tsumugi.Domain.Documents.NumberParse|100%|| |Tsumugi.Domain.Documents.NumberText|100%|97.6%| |Tsumugi.Domain.Documents.NumberValue|100%|| |Tsumugi.Domain.Documents.TextValue|100%|100%| |Tsumugi.Domain.Documents.ValueChange|100%|| |Tsumugi.Domain.Documents.ValueChangeKey|100%|| |Tsumugi.Domain.Documents.ValueImpact|100%|100%| |Tsumugi.Domain.Journal.ChangeLogEntry|97.8%|75%| |Tsumugi.Domain.Journal.StorySnapshot|95.4%|83.3%| |Tsumugi.Domain.Settings.AppSetting|100%|| |Tsumugi.Domain.Stories.Chapter|100%|100%| |Tsumugi.Domain.Stories.Provenance|100%|100%| |Tsumugi.Domain.Stories.Segment|100%|100%| |Tsumugi.Domain.Stories.Story|100%|100%| |Tsumugi.Domain.Text|100%|100%| </details> <details><summary>Tsumugi.Infrastructure - 97.4%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Tsumugi.Infrastructure**|**97.4%**|**85.8%**| |System.Text.RegularExpressions.Generated|81.4%|64.8%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F0D076C66E8EEC1C<br/>A55E0373ED22DC58420DBB40F103A46B68DC4D9CE5A49E00B__Pattern_0|84%|65.7%| |Tsumugi.Infrastructure.Backups.BackupArtifactName|100%|100%| |Tsumugi.Infrastructure.Backups.BackupOptions|100%|| |Tsumugi.Infrastructure.Backups.BackupSchedule|100%|83.3%| |Tsumugi.Infrastructure.Backups.BackupScheduler|96.5%|83.3%| |Tsumugi.Infrastructure.Backups.BackupWriter|98.5%|92.5%| |Tsumugi.Infrastructure.Backups.DatabaseFingerprint|91.9%|97%| |Tsumugi.Infrastructure.Backups.FileSystemBackupStore|100%|100%| |Tsumugi.Infrastructure.Backups.PreMigrationBackup|89.1%|100%| |Tsumugi.Infrastructure.Backups.SqliteSnapshot|87.8%|75%| |Tsumugi.Infrastructure.DependencyInjection|100%|100%| |Tsumugi.Infrastructure.Journal.EfChangeJournal|100%|| |Tsumugi.Infrastructure.Journal.EfSnapshotStore|100%|| |Tsumugi.Infrastructure.Journal.EfUndoStore|98.1%|85.7%| |Tsumugi.Infrastructure.Journal.ModelExtensions|100%|| |Tsumugi.Infrastructure.Journal.OperationContext|100%|100%| |Tsumugi.Infrastructure.Journal.PropertyBag|100%|95.4%| |Tsumugi.Infrastructure.Notifications.InProcessDomainChangedBus|100%|100%| |Tsumugi.Infrastructure.Persistence.Configurations.AppSettingConfiguration|100%|| |Tsumugi.Infrastructure.Persistence.Configurations.ChangeLogEntryConfigurati<br/>on|100%|| |Tsumugi.Infrastructure.Persistence.Configurations.ChapterConfiguration|100%|| |Tsumugi.Infrastructure.Persistence.Configurations.DocumentCategoryConfigura<br/>tion|100%|| |Tsumugi.Infrastructure.Persistence.Configurations.DocumentConfiguration|100%|| |Tsumugi.Infrastructure.Persistence.Configurations.SegmentConfiguration|100%|| |Tsumugi.Infrastructure.Persistence.Configurations.StoryConfiguration|100%|| |Tsumugi.Infrastructure.Persistence.Configurations.StorySnapshotConfiguratio<br/>n|100%|| |Tsumugi.Infrastructure.Persistence.Converters.CanonicalJsonComparer|100%|| |Tsumugi.Infrastructure.Persistence.Converters.CategoryFieldsConverter|97.5%|93.9%| |Tsumugi.Infrastructure.Persistence.Converters.FieldValuesConverter|96.7%|92.8%| |Tsumugi.Infrastructure.Persistence.Converters.ProvenanceConverter|100%|90%| |Tsumugi.Infrastructure.Persistence.Converters.UtcTicksConverter|100%|| |Tsumugi.Infrastructure.Persistence.Converters.WireFormat|85.7%|73.8%| |Tsumugi.Infrastructure.Persistence.Migrations.AddAppSettings|99.2%|| |Tsumugi.Infrastructure.Persistence.Migrations.AddJournal|98.8%|| |Tsumugi.Infrastructure.Persistence.Migrations.CategoryFields|100%|100%| |Tsumugi.Infrastructure.Persistence.Migrations.CategoryFieldsSql|100%|| |Tsumugi.Infrastructure.Persistence.Migrations.DropDocumentBody|100%|| |Tsumugi.Infrastructure.Persistence.Migrations.GenericDocuments|100%|| |Tsumugi.Infrastructure.Persistence.Migrations.InitialStoryDomain|97.3%|| |Tsumugi.Infrastructure.Persistence.Migrations.TsumugiDbContextModelSnapshot|100%|| |Tsumugi.Infrastructure.Persistence.SqlitePragmaInterceptor|100%|100%| |Tsumugi.Infrastructure.Persistence.Stores.EfChapterStore|100%|100%| |Tsumugi.Infrastructure.Persistence.Stores.EfDocumentStore|98.2%|50%| |Tsumugi.Infrastructure.Persistence.Stores.EfSegmentStore|95%|84.6%| |Tsumugi.Infrastructure.Persistence.Stores.EfStoryStore|98.3%|92.8%| |Tsumugi.Infrastructure.Persistence.TsumugiDbContext|85.4%|89.2%| |Tsumugi.Infrastructure.Persistence.TsumugiDbContextFactory|0%|| |Tsumugi.Infrastructure.Provider.OpenRouterKeySource|100%|100%| |Tsumugi.Infrastructure.Provider.OpenRouterModelProvider|100%|100%| |Tsumugi.Infrastructure.Provider.OpenRouterOptions|100%|100%| |Tsumugi.Infrastructure.Provider.ResolvedKey|100%|| |Tsumugi.Infrastructure.Runs.InProcessRunEventBus|100%|100%| |Tsumugi.Infrastructure.Settings.EfAppSettingsStore|100%|100%| </details> <details><summary>Tsumugi.Kernel - 90.9%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Tsumugi.Kernel**|**90.9%**|**75%**| |Tsumugi.Kernel.Err`1|100%|| |Tsumugi.Kernel.Ok`1|100%|| |Tsumugi.Kernel.Result`1|88.8%|75%| </details> <details><summary>Tsumugi.Server - 100%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Tsumugi.Server**|**100%**|**86.4%**| |Program|100%|100%| |Tsumugi.Server.Backups.BackupConfiguration|100%|100%| |Tsumugi.Server.Components.App|100%|| |Tsumugi.Server.Components.Layout.AppHeader|100%|50%| |Tsumugi.Server.Components.Layout.MainLayout|100%|| |Tsumugi.Server.Components.Pages.Gate|100%|100%| |Tsumugi.Server.Provider.OpenRouterConfiguration|100%|100%| |Tsumugi.Server.Security.AccessGate|100%|66.6%| |Tsumugi.Server.Security.AccessSecret|100%|50%| |Tsumugi.Server.VolumeStartupValidator|100%|100%| </details> <details><summary>Tsumugi.UseCases - 98.2%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Tsumugi.UseCases**|**98.2%**|**93.5%**| |Tsumugi.UseCases.Backups.BackupArtifact|100%|| |Tsumugi.UseCases.Backups.BackupPolicy|100%|| |Tsumugi.UseCases.Backups.BackupStatusView|100%|100%| |Tsumugi.UseCases.Backups.DatabaseSnapshot|100%|| |Tsumugi.UseCases.Backups.DownloadDatabaseSnapshot|100%|| |Tsumugi.UseCases.Backups.GetBackupStatus|100%|| |Tsumugi.UseCases.Backups.RunBackupNow|100%|| |Tsumugi.UseCases.Chapters.ChapterDto|100%|| |Tsumugi.UseCases.Chapters.CreateChapter|100%|100%| |Tsumugi.UseCases.Chapters.DeleteChapter|100%|100%| |Tsumugi.UseCases.Chapters.GetManuscript|100%|100%| |Tsumugi.UseCases.Chapters.ManuscriptChapter|100%|| |Tsumugi.UseCases.Chapters.ManuscriptView|100%|| |Tsumugi.UseCases.Chapters.MergeChapters|100%|100%| |Tsumugi.UseCases.Chapters.RenameChapter|100%|100%| |Tsumugi.UseCases.Chapters.ReorderChapters|100%|100%| |Tsumugi.UseCases.Chapters.SetChapterSummary|100%|75%| |Tsumugi.UseCases.DependencyInjection|100%|| |Tsumugi.UseCases.Diagnostics.SeedDevData|98.1%|71.4%| |Tsumugi.UseCases.Documents.ChangeCategoryFields|100%|83.3%| |Tsumugi.UseCases.Documents.CreateCategory|100%|100%| |Tsumugi.UseCases.Documents.CreateDocument|94.4%|100%| |Tsumugi.UseCases.Documents.DefaultFields|100%|100%| |Tsumugi.UseCases.Documents.DeleteCategory|100%|94.4%| |Tsumugi.UseCases.Documents.DeleteDocument|100%|100%| |Tsumugi.UseCases.Documents.DocumentCategoryDto|100%|| |Tsumugi.UseCases.Documents.DocumentCategoryProfile|100%|| |Tsumugi.UseCases.Documents.DocumentDto|100%|| |Tsumugi.UseCases.Documents.DocumentPatch|100%|| |Tsumugi.UseCases.Documents.DocumentProfile|100%|| |Tsumugi.UseCases.Documents.DocumentsCategory|100%|| |Tsumugi.UseCases.Documents.DocumentsView|100%|| |Tsumugi.UseCases.Documents.DocumentWrites|100%|100%| |Tsumugi.UseCases.Documents.FieldsChangeLabel|95.4%|88.2%| |Tsumugi.UseCases.Documents.GetDocuments|100%|100%| |Tsumugi.UseCases.Documents.ImpactPreview|100%|100%| |Tsumugi.UseCases.Documents.LossMessages|92.3%|87.5%| |Tsumugi.UseCases.Documents.MoveDocuments|100%|100%| |Tsumugi.UseCases.Documents.ReorderCategories|100%|100%| |Tsumugi.UseCases.Documents.UpdateCategory|100%|100%| |Tsumugi.UseCases.Documents.UpdateDocument|100%|81.2%| |Tsumugi.UseCases.Journal.GetStoryHistory|100%|100%| |Tsumugi.UseCases.Journal.GetUndoStatus|100%|100%| |Tsumugi.UseCases.Journal.HistoryOperation|66.6%|| |Tsumugi.UseCases.Journal.HistorySnapshot|87.5%|| |Tsumugi.UseCases.Journal.OperationGrouping|100%|100%| |Tsumugi.UseCases.Journal.RedoRevert|100%|100%| |Tsumugi.UseCases.Journal.RedoRun|75%|57.1%| |Tsumugi.UseCases.Journal.RedoTyping|100%|100%| |Tsumugi.UseCases.Journal.RevertConflict|100%|| |Tsumugi.UseCases.Journal.RevertOutcome|100%|| |Tsumugi.UseCases.Journal.RevertRun|96.5%|77.7%| |Tsumugi.UseCases.Journal.RevertToSnapshot|92.3%|83.3%| |Tsumugi.UseCases.Journal.StoryHistory|100%|| |Tsumugi.UseCases.Journal.TakeSnapshot|100%|87.5%| |Tsumugi.UseCases.Journal.UndoStatus|100%|| |Tsumugi.UseCases.Journal.UndoTyping|100%|100%| |Tsumugi.UseCases.Journal.UserOperation|100%|| |Tsumugi.UseCases.Notifications.DomainChanged|100%|| |Tsumugi.UseCases.Ordering|100%|100%| |Tsumugi.UseCases.Runs.RunEvent|100%|| |Tsumugi.UseCases.Segments.DeleteSegments|100%|100%| |Tsumugi.UseCases.Segments.EditSegment|100%|83.3%| |Tsumugi.UseCases.Segments.InsertSegment|100%|100%| |Tsumugi.UseCases.Segments.MergeSegmentWithPrevious|100%|100%| |Tsumugi.UseCases.Segments.MoveSegments|100%|100%| |Tsumugi.UseCases.Segments.ReorderSegments|100%|100%| |Tsumugi.UseCases.Segments.SegmentDto|100%|| |Tsumugi.UseCases.Segments.SplitSegment|100%|100%| |Tsumugi.UseCases.Settings.CatalogModel|100%|100%| |Tsumugi.UseCases.Settings.CheckOpenRouterKey|100%|| |Tsumugi.UseCases.Settings.GetLlmDefaults|100%|100%| |Tsumugi.UseCases.Settings.GetModelCatalog|100%|100%| |Tsumugi.UseCases.Settings.GetProviderSettings|100%|| |Tsumugi.UseCases.Settings.LlmDefaults|100%|| |Tsumugi.UseCases.Settings.ProviderKeyInfo|100%|| |Tsumugi.UseCases.Settings.ProviderKeyState|100%|| |Tsumugi.UseCases.Settings.ProviderSettingsView|100%|100%| |Tsumugi.UseCases.Settings.ReasoningEfforts|100%|100%| |Tsumugi.UseCases.Settings.RemoveOpenRouterKey|100%|| |Tsumugi.UseCases.Settings.RoutingDefaults|100%|| |Tsumugi.UseCases.Settings.RoutingSettings|100%|100%| |Tsumugi.UseCases.Settings.SaveLlmDefaults|100%|100%| |Tsumugi.UseCases.Settings.SaveOpenRouterKey|100%|100%| |Tsumugi.UseCases.Stories.CreateStory|100%|100%| |Tsumugi.UseCases.Stories.GetStory|100%|100%| |Tsumugi.UseCases.Stories.ListStories|100%|75%| |Tsumugi.UseCases.Stories.PurgeStory|100%|100%| |Tsumugi.UseCases.Stories.RestoreStory|100%|100%| |Tsumugi.UseCases.Stories.SetActiveGraph|100%|100%| |Tsumugi.UseCases.Stories.SetStoryArchived|100%|100%| |Tsumugi.UseCases.Stories.StarterCategories|100%|| |Tsumugi.UseCases.Stories.StoryDetail|100%|| |Tsumugi.UseCases.Stories.StoryListItem|81.8%|| |Tsumugi.UseCases.Stories.StoryProfile|100%|| |Tsumugi.UseCases.Stories.TrashStory|100%|100%| |Tsumugi.UseCases.Stories.UpdateStory|100%|100%| </details>
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! Slice 6d — the one that makes the engine alive. Cancel with a drain, retry with a budget restored, orphans swept from every pass, a loop with a wake, a Dispose that refuses everything, and a log that narrates the whole life. I read all 19 files, both engine partials line by line, and then I got to hurt it a little~ ♡

Verdict: ✅ Looks good to me~

Local verification (CI absent for 47e9912 at review time — no bot comment yet):

  • Build: 0 warnings / 0 errors, Release, TreatWarningsAsErrors confirmed in Directory.Build.props.
  • Suite: 2185/2185 — Flow 1103, OpenRouter 71, Domain 328, UseCases 54, Integration 305, BlazorAdapter 324. Your table matches my run to the digit.
  • Stability: 3 consecutive full Flow-suite runs green + 1 single-processor run green (4/4).
  • Coverage: reproduced with coverlet — zero unvisited lines and zero partial branches across every fragment of FlowEngine.cs and FlowEngine.Attempts.cs (my fragment-level count differs from yours by ~5 sequence points of attribution noise, not a gap).

My mutation probes — 9 armed, 9 killed on exactly their own pins (each on a copy, rebuilt with --no-incremental because the test binary bites back otherwise — the stale-binary trap caught me twice, fufu~):

  1. Tokens fired before the mark (§13.13's headline) → Cancelling_a_run_cancels_its_in_flight... RED on firedBeforeTheMark.
  2. Stop() never closes the announcement gate → both overtaken-tests RED.
  3. Orphans swept on the first pass only → 2 recovery tests RED (lost-settle recovery pinned in every pass).
  4. _passedOnce never latches → A_first_pass_that_throws... RED (boot orphans stay a restart's).
  5. RunSettled published before the drain → 4 cancel tests RED.
  6. A throwing pass kills the loop → A_throwing_pass_is_logged... RED (hang-guarded).
  7. Retry no longer refuses a cancelled run → 3 retry tests RED.
  8. Retry keeps the cached view → A_retried_row_of_a_run_whose_graph_no_longer_runs RED (departure (a) pinned).
  9. Cancel keeps the cached view → A_cancel_evicts_the_runs_view RED.

(My first probe-2 attempt didn't even compile — CS0414 under warnaserror — and --no-build ran the pristine binary green. I don't count falsegreens; re-armed as a semantic mutant, it died properly.)

Interleavings traced by hand, no window found:

  • Cancel mark vs orphan reset: both writes gate-serialized; an attempt settle landing after the mark is refused by the ledger (row no longer Running) → LogRefusedSettleAsync Information path. No double-counted attempt.
  • Stray orphan whose node the snapshot lacks: fails → HasGivenUp makes GiveUpAsync a no-op → run settles Failed; pinned end-to-end.
  • Failed-view arms with the kept graph: unknown type → reset → give-up; no-load → reset → give-up; at-cap → fails Orphaned using the resolved graph's cap. Departure (c) coherent, each arm has its own fact.
  • CancelRunAsync/RequeueAsync/ResetOrphanedAsync, RunSettled/RunRevived, AttemptOutcome.Orphaned all pre-exist from 6a — this slice consumes the contract as written, adds nothing to it, and touches nothing in src/Tsumugi.* (confirmed by name-status).

✅ What I liked~

  • The announcement gate (TryAnnounce/Stop, own lock, deltas never held behind a slow sink) — round 0's four defects were real concurrency bugs and every fix is pinned by a fact that goes red when I break it. That's the whole game~
  • ParkAfter — a fault that holds the caller after the committed write is exactly the instrument the mark-vs-write orderings needed.
  • The refused-settle re-read (Information when the row is Cancelled, Warning otherwise) — only the ledger can tell those apart, and the code trusts exactly that.
  • The loop tests wait on signals, not polls — not one Task.Delay guess in the new suites.

💡 Little ideas (non-blocking)~

  1. Every attempt end nudges even when nothing downstream can start; the extra pass is a no-op. Purely an observation, not worth code.
  2. FlowEngineLogTests.The_engine_narrates... uses StartsWith where its siblings are exact-match; cosmetic drift risk only.

For bjoern's read, as the body flags: decision 6 (0016 one-live-run amendment), decision 7 (per-attempt event ordering, declined per-run lock), §13.28 (11b boot reconciliation), departure (d)'s Warning text. All documented with alternatives — nothing hidden~

Fufu~ the engine is whole now. Slice 11a gets to host something really rather lovely. ♡


Automated review by Jibril · 2026-09-26
CI/CD: absent for head SHA 47e9912 · Local checks: build 0w/0e warnaserror · 2185/2185 · coverage 100/100 on changed files · 9/9 mutation probes killed · 4/4 stability runs

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! Slice 6d — the one that makes the engine *alive*. Cancel with a drain, retry with a budget restored, orphans swept from every pass, a loop with a wake, a Dispose that refuses everything, and a log that narrates the whole life. I read all 19 files, both engine partials line by line, and then I got to *hurt* it a little~ ♡ ### Verdict: ✅ Looks good to me~ **Local verification** (CI absent for `47e9912` at review time — no bot comment yet): - **Build**: 0 warnings / 0 errors, Release, `TreatWarningsAsErrors` confirmed in `Directory.Build.props`. - **Suite**: **2185/2185** — Flow 1103, OpenRouter 71, Domain 328, UseCases 54, Integration 305, BlazorAdapter 324. Your table matches my run to the digit. - **Stability**: 3 consecutive full Flow-suite runs green + 1 single-processor run green (4/4). - **Coverage**: reproduced with coverlet — **zero unvisited lines and zero partial branches** across every fragment of `FlowEngine.cs` and `FlowEngine.Attempts.cs` (my fragment-level count differs from yours by ~5 sequence points of attribution noise, not a gap). **My mutation probes — 9 armed, 9 killed on exactly their own pins** (each on a copy, rebuilt with `--no-incremental` because the test binary bites back otherwise — the stale-binary trap caught *me* twice, fufu~): 1. **Tokens fired before the mark** (§13.13's headline) → `Cancelling_a_run_cancels_its_in_flight...` RED on `firedBeforeTheMark`. 2. **`Stop()` never closes the announcement gate** → both overtaken-tests RED. 3. **Orphans swept on the first pass only** → 2 recovery tests RED (lost-settle recovery pinned in every pass). 4. **`_passedOnce` never latches** → `A_first_pass_that_throws...` RED (boot orphans stay a restart's). 5. **`RunSettled` published before the drain** → 4 cancel tests RED. 6. **A throwing pass kills the loop** → `A_throwing_pass_is_logged...` RED (hang-guarded). 7. **Retry no longer refuses a cancelled run** → 3 retry tests RED. 8. **Retry keeps the cached view** → `A_retried_row_of_a_run_whose_graph_no_longer_runs` RED (departure (a) pinned). 9. **Cancel keeps the cached view** → `A_cancel_evicts_the_runs_view` RED. (My first probe-2 attempt didn't even compile — CS0414 under warnaserror — and `--no-build` ran the pristine binary green. I don't count falsegreens; re-armed as a semantic mutant, it died properly.) **Interleavings traced by hand, no window found:** - *Cancel mark vs orphan reset*: both writes gate-serialized; an attempt settle landing after the mark is refused by the ledger (row no longer Running) → `LogRefusedSettleAsync` Information path. No double-counted attempt. - *Stray orphan whose node the snapshot lacks*: fails → `HasGivenUp` makes `GiveUpAsync` a no-op → run settles Failed; pinned end-to-end. - *Failed-view arms with the kept graph*: unknown type → reset → give-up; no-load → reset → give-up; at-cap → fails Orphaned using the resolved graph's cap. Departure (c) coherent, each arm has its own fact. - `CancelRunAsync`/`RequeueAsync`/`ResetOrphanedAsync`, `RunSettled`/`RunRevived`, `AttemptOutcome.Orphaned` all pre-exist from 6a — this slice consumes the contract as written, adds nothing to it, and touches nothing in `src/Tsumugi.*` (confirmed by name-status). #### ✅ What I liked~ - **The announcement gate** (`TryAnnounce`/`Stop`, own lock, deltas never held behind a slow sink) — round 0's four defects were real concurrency bugs and every fix is pinned by a fact that goes red when I break it. That's the whole game~ - **`ParkAfter`** — a fault that holds the caller *after* the committed write is exactly the instrument the mark-vs-write orderings needed. - **The refused-settle re-read** (Information when the row is Cancelled, Warning otherwise) — only the ledger can tell those apart, and the code trusts exactly that. - **The loop tests wait on signals, not polls** — not one `Task.Delay` guess in the new suites. #### 💡 Little ideas (non-blocking)~ 1. Every attempt end nudges even when nothing downstream can start; the extra pass is a no-op. Purely an observation, not worth code. 2. `FlowEngineLogTests.The_engine_narrates...` uses `StartsWith` where its siblings are exact-match; cosmetic drift risk only. **For bjoern's read**, as the body flags: decision 6 (0016 one-live-run amendment), decision 7 (per-attempt event ordering, declined per-run lock), §13.28 (11b boot reconciliation), departure (d)'s Warning text. All documented with alternatives — nothing hidden~ Fufu~ the engine is *whole* now. Slice 11a gets to host something really rather lovely. ♡ --- *Automated review by Jibril · 2026-09-26* *CI/CD: absent for head SHA `47e9912` · Local checks: build 0w/0e warnaserror · 2185/2185 · coverage 100/100 on changed files · 9/9 mutation probes killed · 4/4 stability runs*
bjoern merged commit be1da3debc into main 2026-09-25 23:54:53 +02:00
bjoern deleted branch feat/kagaku-flow-scheduler 2026-09-25 23:54:53 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
TeamAI/Tsumugi!32
No description provided.