feat: the llm node runs (Phase 2 · slice 8c) #40

Merged
bjoern merged 7 commits from feat/llm-executor into main 2026-09-26 10:48:18 +02:00
Member

Slice 8c of Phase 2, the last of three PRs for the llm node (8a, #35: the gateway streams; 8b, #36: the node's config and request). This PR makes the node run: its executor, registered with the descriptor and the strict-schema rule, reads the answer, records what was sent and what it cost on every attempt, and fails permanently where a retry would only spend money. The package gets a README of its rules. Nothing in Tsumugi starts a run yet: that is the Run button (slice 12).

Commits:

  • a6250c2 is the feature.
  • 82eab26 moves the OpenRouter.Net submodule to b002f5e, the merge of #28 (strict provider preferences, from 8b's review).
  • 448d496 adapts LC9 to #28's stricter reading.
  • be04bfe holds the fixes from the review I ran before opening (round 0, below).
  • b360bba holds the fixes from the second pass (below).
  • 3cbacc8 writes this PR's number into ADR 0016, 0017, 0018 and the plan.
  • afb83fc records the live context-length answer (no error_type) in ADR 0017 and README LO8.

What

Kagaku.Flow.OpenRouter.Llm, public:

  • LlmExecutor : INodeExecutor, one attempt of an llm node (README LX1–LX10):
    • Before anything is sent, these fail the row at once: a config that does not read, a schema that cannot be inlined or read, a model missing from the catalog, and, with a schema, a model whose entry does not list structured_outputs. A catalog that cannot load is transient.
    • The install-wide defaults are read once per attempt and never cached.
    • The request streams through the gateway. Reasoning deltas go on the reasoning channel before their chunk's text, empty fragments publish nothing, and a refusal is never published. Every delta is out before the executor returns.
    • A cancel or a shutdown settles nothing, whether it lands in the stream or while the catalog or the defaults load.
  • AddKagakuFlowLlmNode() registers the descriptor, the executor and the strict-schema rule as enumerable singletons, once however often it is called. It adds a template host over the registered extensions unless one is registered.
  • LlmStrictSchemaRule: when its strict check finds nothing, it also builds the schema as the executor does. A schema JsonSchema.Net cannot read is refused (openrouter.schema-not-readable), so a start is refused where a run would fail.

Internal:

  • LlmAnswer reads a completion as the node's outcome (LO3, LO5, LO6).
    • Text answer:
      • a refusal or a content filter → permanent;
      • a cut-off at max_tokens before any text → permanent, with advice;
      • no message, a provider error, a tool call or no text → transient;
      • a cut-off with text → succeeds, and the finish reason is recorded.
    • Structured answer:
      • ParseJson's outcome, in the library's words and by its kind, except a tool call, which gets the node's sentence;
      • then JsonSchema.Net's check: a mismatch is transient and names its first three places; a ref it cannot resolve is permanent.
  • LlmFacts fills the attempt's facts (LO9): the body as it went over the wire, the details, the requested model and characters sent; from the answer, or from what the stream delivered when the call failed, the served model, provider, tier, finish reason, generation, reasoning and tokens; and the cost, failures included.
  • LlmFailureText.Describe turns a failed call into one sentence per kind (LO7). A length rejection names the model, the characters sent and the catalog's window (LO8).
  • LlmSchemaCheck builds a schema into a registry of its own and checks the answer against it.

Tsumugi: DependencyInjection calls AddKagakuFlowLlmNode() beside the gateway. ProviderCompositionTests checks that the type and its rule are registered.

Docs:

  • the package README (LG, LC, LS, LB, LD, LQ, LX, LO, LR), host-neutral;
  • ADR 0016, 0017 and 0018 amendments (below);
  • docs/stories/settings.md: Routing ticked, since the node now uses the defaults at run time;
  • the plan names this PR and #28.

Why

  • ADR 0016: a node type needs its executor, and the NodeRegistry refuses a descriptor without one, so 8b's descriptor is registered here.
  • ADR 0018: the node's runtime (streaming, structured output, caching, routing, facts).
  • ADR 0017: a length rejection is permanent and names the overflow.
  • ADR 0024: RenderedMessagesJson is exactly what went on the wire.

Decisions to confirm (brief §13)

# Decision ADR sentence it amends
5 A length rejection is read from the documented error shape (error_type: context_length_exceeded), and LO8 names the model, the characters sent and the catalog's window. Without error_type it is a 400: still permanent, in the library's words. The documented error carries no token count, so a rejected row teaches the ratio nothing. 0017: "the ratio learns from the token count it reports" is retracted
10 A model without structured_outputs fails at run time, not at validation. A graph rule does no I/O and has no catalog. The Run button's preflight (12) and Phase 3's picker can say it earlier. 0018 Status line
11 Text answers by finish reason. A cut-off at max_tokens with text succeeds: prose that stops at the budget is still prose. Changed in round 0: a cut-off before any text is permanent, like a structured cut-off, because the same request meets the same budget. A refusal and a content filter are permanent; with a schema, ParseJson's refusal, filter and cut-off are permanent too. 0016 and 0018: these were "transient"
15 A model missing from the catalog fails permanently before sending; a catalog that cannot load is transient. —
29 Deltas: reasoning before text within a chunk; a refusal is not streamed; with a schema the JSON streams on text like any answer. —
40 The playground's palette waits for slice 9. SampleGraph's literal stays; The_canvas_entry_equals_the_playgrounds_llm_literal keeps the registry's llm entry equal to it, so slice 9's switch changes nothing visible. —
41 Agent mode's keys (tools, documents, roundBudget) are refused until Phase 4. Phase gating, not a change of ADR 0018; LC1 says so. —
— A schema JsonSchema.Net cannot build is permanent (LX2), and since round 0 it is refused at start (LS3). A schema without $schema is read as draft 2020-12. An unknown dialect such as draft-04 is named in the writer's words, with the URIs to name instead. A ref the validator can resolve only when an answer reaches it (a nested $id) fails the row after that one paid attempt. 0018 Status line and body; 0016's permanent list
— Provider choices the library does not know are refused at their own pointer with the spellings it takes (round 0, E2). One example: "A precision is int4, int8, … fp32 or unknown, not "fp9"." This replaces the library's ".NET" sentence. —

ADR 0016, 0017 and 0018 each get a Status line, Amended 2026-09-26 (PR #40, slice 8c).

Review before opening (round 0)

Before opening, I ran a review workflow. It had three reviewers, one each for the executor in the engine, the docs, and the config's adaptation to #28. It also re-checked six correctness points I had found myself. A skeptic tried to refute every point.

The results: 20 confirmed, 3 partly confirmed (E1, E3, E4) and 1 refuted (D10). A tests reviewer did not finish, so the second pass took on that dimension. All confirmed points are fixed in be04bfe, except E3's wrong-kind members other than max_price. D9 was fixed by rewording a6250c2's message.

Behaviour:

  • A1: JsonSchema.Net resolves a ref only when the evaluation reaches it. So a ref it cannot resolve, such as a nested $id re-basing #/$defs/x, threw after the paid call. That happened three times, with no cost recorded. It is now a permanent failure with the attempt's facts.
  • A2: a text answer whose reasoning used up max_tokens before any text was retried three times as "no text". It is now permanent, with the advice to raise max_tokens or lower the effort.
  • A4: a schema JsonSchema.Net cannot build (a draft-04 $schema, a pattern that is not a regular expression) passed the start and failed at run time with a developer's hint. It is now refused at start.
  • A6: the length sentence claimed the catalog's window as the endpoint's. It now says it is the catalog's, and a window of 0 is none.
  • E2: a data collection policy, precision, sort or partition the library does not know reached the writer as "Provider quantizations contains Unspecified … Use Quantization.Unknown …". Each is now refused at its pointer, with the spellings.
  • E4, D1: the provider's names are checked in one pass, in document order. A max_price that is not an object is named. LC12 now describes the order the code keeps. E1: LC12 and Read say where the provider's reading stops early, because the library stops at its first refusal.

Tests:

  • B1: the cancel test could not fail on the executor. It now asserts the engine's log line. This kills the brief's one LX8 "equivalent mutant", an executor that catches the cancel.
  • B2: a shutdown while the catalog or the defaults load.
  • B3: empty content and reasoning fragments, as OpenRouter's first chunk and its reasoning chunks carry them.
  • B4: the template host the node registers without the core.
  • A3: a broken-off attempt's facts come from what the stream delivered.
  • D3: a row for each refusal #28 added.
  • The shared RecordingLogger moves to tests/Shared, in a namespace of its own, as SignallingClock did (brief decision 44).

Docs:

  • D2, D6, D7, D8: these README rules name what the code does:
    • LS2's refs-followed budget;
    • Publish, ClientsFor and the using block;
    • LX2's trimmed full stop;
    • the account setting that can force compression on;
    • what the local schema registry falls back to.
  • A5, D4, D5:
    • ADR 0018's amendment says "with a response schema" and counts #28 as a fifth Phase 2 library PR;
    • ADR 0016 adds text cut off before any output;
    • the plan's risk 4 no longer says a rejection corrects the ratio.

Declined:

  • D10, refuted: "the chapter's preview" in LX6 states ADR 0018's reason, cited by number, while the rule itself is in the engine's terms (RunEventChannel.Reasoning).
  • E3, partly: a wrong-kind provider member other than max_price ("order":"x", "zdr":"true") still reaches the writer as the library's JsonException, with a .NET type name and Path: $. This was so before this branch, and 8b pinned one such row. A follow-up could check each member's kind at its own pointer.

Second pass

A second workflow checked each round-0 fix against its original input and put the defect back to see the new test fail. It also reviewed the tests (the dimension round 0 did not finish) and the fix commit itself. Every new point went to a skeptic.

The checks: every round-0 fix held, except A4 and E1, which were only partly resolved. Their gaps are the findings below. The review raised 22 new points: 20 confirmed, 2 partly confirmed (C8, F4), none refuted. All are taken in b360bba.

The schema, round 0's weak spot:

  • K1, M2, F1, T1: round 0 blamed $schema whenever the schema without it built, or failed another way. But without it JsonSchema.Net 8.0.5 builds under its V1 dialect, which refuses unknown keywords. So a draft-07 or 2020-12 schema with an unrelated error was told its dialect cannot be checked, and was advised to name draft-07 or 2020-12. A dialect is now blamed only when a schema of that $schema alone does not build. The advice names the dialects by their URIs.
  • F2: a $schema that is not an absolute URI ("draft-07") keeps JsonSchema.Net's own sentence, which says it wants one.
  • K2, T2, T3: the start rule built the schema before the strict check, so the strict check's pointed findings ("Strict mode does not support "nullable"; remove it.") were hidden behind one root-level JsonSchema.Net sentence. A plain annotation such as x-order was refused too. Now the strict check runs first, and the schema is built only when it finds nothing. A schema without $schema is read as draft 2020-12 (BuildOptions.Dialect, not the process-wide default), which takes an unknown keyword as an annotation.
  • T4: JsonSchema.Net compares an enum's or a const's numbers as decimals, and throws beyond their range, after the paid call. That is A1's failure through another exception. Such a number in the schema is now refused at start and at LX2. In an answer it is a transient mismatch, and the attempt keeps its facts.
  • M1: ADR 0018 claimed every unreadable schema is refused at start. A1's nested $id passes the start. ADR 0018 and 0016 now say so.

The rest:

  • F4: round 0 skipped Validate() after an unknown choice, which hid a null slug. It now runs over the preferences without the choices.
  • K3: ValidateConfig's and LlmConfigRead's summaries say the provider's reading stops early.
  • F3: Describe keeps its own rule that a window of 0 is none.
  • Tests (C1–C8):
    • the facts of every arm before the request;
    • the finding codes as literals;
    • three and four failing places;
    • blank content, refusal and native reason;
    • two unknown choices and escaped names;
    • the deltas of two concurrent nodes;
    • the requested model under nitro;
    • "copied from a run" comments.
  • F5: sorted usings.

Out of scope (brief §8)

  • Starting a run (12), the monitor (13) and the inspector (14).
  • The palette reading the registry (slice 9).
  • TSUMUGI_FAKE_LLM (D-21, slices 11b and 12).
  • Agent mode (Phase 4).

Tests

Counted from dotnet test (facts plus InlineData rows). The base is main at 11b466e.

Project main this PR
Kagaku.Flow.OpenRouter.Tests 430 596 (592 run, the 4 live checks skipped)
Tsumugi.Integration.Tests 342 343
Kagaku.Flow.Tests 1103 1103
Tsumugi.BlazorAdapter.Tests 351 351
Tsumugi.Domain.Tests 457 457
Tsumugi.UseCases.Tests 155 155
Total 2838 3005 (3001 passed, 4 skipped)

New in the package (166):

  • LlmOutcomeTests: 56 (LO1–LO8, LX1–LX3, LX5).
  • LlmFactsTests: 25 (LO9, LO10, LO12).
  • LlmExecutionTests: 18 (LX4, LX6–LX10, LR2).
  • LlmRegistrationTests: 10 (LR1–LR4).
  • LlmConfigValidationTests: +24 (#28's refusals, the provider's choices and order).
  • LlmFailureTextTests: +18 (LO7, LO8).
  • LlmStrictSchemaRuleTests: +14 (the refs budget, unreadable schemas, the dialect, the order of the check and the reading).
  • LiveOpenRouterChecks: +1, the engine's live run (skipped without a key).

New in Tsumugi: ProviderCompositionTests.The_llm_node_type_is_registered_with_its_rule. The core's tests move RecordingLogger to tests/Shared and keep their count.

Coverage

Coverlet with branch coverage, on the package's tests. 100% of lines and branches on every file this PR changes in the package:

File Lines Branches
Llm/LlmConfig.cs 397/397 246/246
Llm/LlmExecutor.cs 74/74 28/28
Llm/LlmSchemaCheck.cs 71/71 41/41
Llm/LlmFacts.cs 33/33 51/51
Llm/LlmAnswer.cs 35/35 41/41
Llm/LlmFailureText.cs 39/39 33/33
Llm/LlmStrictSchemaRule.cs 38/38 16/16
Llm/LlmDescriptor.cs 130/130 44/44
LlmNodeServiceCollectionExtensions.cs 8/8 —

Mutation probes

Each probe is a single edit, built and tested on its own, one at a time.

  • The brief's 8c probes (§12: 44–56, 68–71, 78) and my own (m1–m12): 46 run.
    • 39 were killed at once.
    • 4 failed to compile first and were rewritten, then killed.
    • m10 (a blank reasoning kept) survived until the blank-reasoning test was added.
    • m6 (the root result skipped in the mismatch list) found dead code, which is removed.
    • m12 is equivalent: reading the catalog by the id as written instead of CatalogId. LC4 keeps routing suffixes out of the model, so the two ids are always equal.
  • Round 0: 16 more, one for each fix, all killed by the new tests. They include A1's uncaught ref, A2's arm, A4's build at start, A6's zero window, E2 and E4, B1's swallowed cancel, both of B2's tokens, B3's two guards, B4's empty host, and A3's received facts.
  • Second pass: 11 more, all killed. They cover:
    • the dialect decision and the URI check;
    • the 2020-12 reading and the strict-first order;
    • T4 on the schema side, on the answer side, and a number that fits;
    • F4's validation, F3's zero window, C7's wire id, and C1's empty request.
  • The brief's equivalent mutants: LX8's "catch the cancel" is no longer equivalent. B1's test kills it through the engine's log. LG10 (8a) and LB5 (8b) were settled in their PRs.
  • Not run: two of the brief's probes, 69b (publish the deltas after the executor returns) and 70d (sort DetailsJson's members).

Browser pass

Slice 9 is not merged, so the playground still reads SampleGraph, and nothing visible changes (brief §16.2, decision 40). 8c does change the host's composition, so I ran the Release build of b360bba, this PR's head (the footer shows the build). It ran with ASPNETCORE_ENVIRONMENT=Development, so scope validation and ValidateOnBuild run at startup, and with the gateway pointed at slice 7's local stub.

  • Startup: the server started, so the composition with AddKagakuFlowLlmNode validates.
  • /graphs/playground: Context → Writer (LLM, accent) → Commit, as before. There were no console errors or warnings. Screenshots were taken light and dark.

Live checks

Run on 2026-09-26 with your one-day debug key; all four passed, about $0.02 in total. The results are in this comment.

  • The engine's run succeeded in one attempt, served by Amazon Bedrock.
  • D-27: both cache markers read back.
  • D-6: OpenRouter's own length rejection carries no error_type. It reads as a refused request, still permanent, and afb83fc records this.

Still yours from before: Continue's model id.

🤖 Generated with Claude Code

Slice 8c of Phase 2, the last of three PRs for the `llm` node (8a, #35: the gateway streams; 8b, #36: the node's config and request). This PR makes the node run: its executor, registered with the descriptor and the strict-schema rule, reads the answer, records what was sent and what it cost on every attempt, and fails permanently where a retry would only spend money. The package gets a README of its rules. Nothing in Tsumugi starts a run yet: that is the Run button (slice 12). **Commits:** - a6250c2 is the feature. - 82eab26 moves the OpenRouter.Net submodule to b002f5e, the merge of #28 (strict provider preferences, from 8b's review). - 448d496 adapts LC9 to #28's stricter reading. - be04bfe holds the fixes from the review I ran before opening (round 0, below). - b360bba holds the fixes from the second pass (below). - 3cbacc8 writes this PR's number into ADR 0016, 0017, 0018 and the plan. - afb83fc records the live context-length answer (no `error_type`) in ADR 0017 and README LO8. ## What **`Kagaku.Flow.OpenRouter.Llm`**, public: - `LlmExecutor : INodeExecutor`, one attempt of an `llm` node (README LX1–LX10): - Before anything is sent, these fail the row at once: a config that does not read, a schema that cannot be inlined or read, a model missing from the catalog, and, with a schema, a model whose entry does not list `structured_outputs`. A catalog that cannot load is transient. - The install-wide defaults are read once per attempt and never cached. - The request streams through the gateway. Reasoning deltas go on the `reasoning` channel before their chunk's text, empty fragments publish nothing, and a refusal is never published. Every delta is out before the executor returns. - A cancel or a shutdown settles nothing, whether it lands in the stream or while the catalog or the defaults load. - `AddKagakuFlowLlmNode()` registers the descriptor, the executor and the strict-schema rule as enumerable singletons, once however often it is called. It adds a template host over the registered extensions unless one is registered. - `LlmStrictSchemaRule`: when its strict check finds nothing, it also builds the schema as the executor does. A schema JsonSchema.Net cannot read is refused (`openrouter.schema-not-readable`), so a start is refused where a run would fail. **Internal:** - `LlmAnswer` reads a completion as the node's outcome (LO3, LO5, LO6). - Text answer: - a refusal or a content filter → permanent; - a cut-off at `max_tokens` before any text → permanent, with advice; - no message, a provider error, a tool call or no text → transient; - a cut-off with text → succeeds, and the finish reason is recorded. - Structured answer: - `ParseJson`'s outcome, in the library's words and by its kind, except a tool call, which gets the node's sentence; - then JsonSchema.Net's check: a mismatch is transient and names its first three places; a ref it cannot resolve is permanent. - `LlmFacts` fills the attempt's facts (LO9): the body as it went over the wire, the details, the requested model and characters sent; from the answer, or from what the stream delivered when the call failed, the served model, provider, tier, finish reason, generation, reasoning and tokens; and the cost, failures included. - `LlmFailureText.Describe` turns a failed call into one sentence per kind (LO7). A length rejection names the model, the characters sent and the catalog's window (LO8). - `LlmSchemaCheck` builds a schema into a registry of its own and checks the answer against it. **Tsumugi:** `DependencyInjection` calls `AddKagakuFlowLlmNode()` beside the gateway. `ProviderCompositionTests` checks that the type and its rule are registered. **Docs:** - the package README (LG, LC, LS, LB, LD, LQ, LX, LO, LR), host-neutral; - ADR 0016, 0017 and 0018 amendments (below); - `docs/stories/settings.md`: Routing ticked, since the node now uses the defaults at run time; - the plan names this PR and #28. ## Why - ADR 0016: a node type needs its executor, and the `NodeRegistry` refuses a descriptor without one, so 8b's descriptor is registered here. - ADR 0018: the node's runtime (streaming, structured output, caching, routing, facts). - ADR 0017: a length rejection is permanent and names the overflow. - ADR 0024: `RenderedMessagesJson` is exactly what went on the wire. ## Decisions to confirm (brief §13) | # | Decision | ADR sentence it amends | |---|---|---| | 5 | **A length rejection is read from the documented error shape** (`error_type: context_length_exceeded`), and LO8 names the model, the characters sent and the catalog's window. Without `error_type` it is a 400: still permanent, in the library's words. The documented error carries no token count, so a rejected row teaches the ratio nothing. | 0017: "the ratio learns from the token count it reports" is retracted | | 10 | **A model without `structured_outputs` fails at run time, not at validation.** A graph rule does no I/O and has no catalog. The Run button's preflight (12) and Phase 3's picker can say it earlier. | 0018 Status line | | 11 | **Text answers by finish reason.** A cut-off at `max_tokens` with text **succeeds**: prose that stops at the budget is still prose. Changed in round 0: a cut-off *before any text* is permanent, like a structured cut-off, because the same request meets the same budget. A refusal and a content filter are permanent; with a schema, `ParseJson`'s refusal, filter and cut-off are permanent too. | 0016 and 0018: these were "transient" | | 15 | **A model missing from the catalog fails permanently before sending**; a catalog that cannot load is transient. | — | | 29 | **Deltas:** reasoning before text within a chunk; a refusal is not streamed; with a schema the JSON streams on `text` like any answer. | — | | 40 | **The playground's palette waits for slice 9.** `SampleGraph`'s literal stays; `The_canvas_entry_equals_the_playgrounds_llm_literal` keeps the registry's `llm` entry equal to it, so slice 9's switch changes nothing visible. | — | | 41 | **Agent mode's keys (`tools`, `documents`, `roundBudget`) are refused until Phase 4.** Phase gating, not a change of ADR 0018; LC1 says so. | — | | — | **A schema JsonSchema.Net cannot build is permanent** (LX2), and since round 0 it is refused at start (LS3). A schema without `$schema` is read as draft 2020-12. An unknown dialect such as draft-04 is named in the writer's words, with the URIs to name instead. A ref the validator can resolve only when an answer reaches it (a nested `$id`) fails the row after that one paid attempt. | 0018 Status line and body; 0016's permanent list | | — | **Provider choices the library does not know** are refused at their own pointer with the spellings it takes (round 0, E2). One example: "A precision is int4, int8, … fp32 or unknown, not "fp9"." This replaces the library's ".NET" sentence. | — | ADR 0016, 0017 and 0018 each get a Status line, **Amended 2026-09-26 (PR #40, slice 8c)**. ## Review before opening (round 0) Before opening, I ran a review workflow. It had three reviewers, one each for the executor in the engine, the docs, and the config's adaptation to #28. It also re-checked six correctness points I had found myself. A skeptic tried to refute every point. The results: 20 confirmed, 3 partly confirmed (E1, E3, E4) and 1 refuted (D10). A tests reviewer did not finish, so the second pass took on that dimension. All confirmed points are fixed in be04bfe, except E3's wrong-kind members other than `max_price`. D9 was fixed by rewording a6250c2's message. **Behaviour:** - **A1:** JsonSchema.Net resolves a ref only when the evaluation reaches it. So a ref it cannot resolve, such as a nested `$id` re-basing `#/$defs/x`, threw after the paid call. That happened three times, with no cost recorded. It is now a permanent failure with the attempt's facts. - **A2:** a text answer whose reasoning used up `max_tokens` before any text was retried three times as "no text". It is now permanent, with the advice to raise `max_tokens` or lower the effort. - **A4:** a schema JsonSchema.Net cannot build (a draft-04 `$schema`, a pattern that is not a regular expression) passed the start and failed at run time with a developer's hint. It is now refused at start. - **A6:** the length sentence claimed the catalog's window as the endpoint's. It now says it is the catalog's, and a window of 0 is none. - **E2:** a data collection policy, precision, sort or partition the library does not know reached the writer as "Provider quantizations contains Unspecified … Use Quantization.Unknown …". Each is now refused at its pointer, with the spellings. - **E4, D1:** the provider's names are checked in one pass, in document order. A `max_price` that is not an object is named. LC12 now describes the order the code keeps. **E1:** LC12 and `Read` say where the provider's reading stops early, because the library stops at its first refusal. **Tests:** - **B1:** the cancel test could not fail on the executor. It now asserts the engine's log line. This kills the brief's one LX8 "equivalent mutant", an executor that catches the cancel. - **B2:** a shutdown while the catalog or the defaults load. - **B3:** empty content and reasoning fragments, as OpenRouter's first chunk and its reasoning chunks carry them. - **B4:** the template host the node registers without the core. - **A3:** a broken-off attempt's facts come from what the stream delivered. - **D3:** a row for each refusal #28 added. - The shared `RecordingLogger` moves to `tests/Shared`, in a namespace of its own, as `SignallingClock` did (brief decision 44). **Docs:** - **D2, D6, D7, D8:** these README rules name what the code does: - LS2's refs-followed budget; - `Publish`, `ClientsFor` and the `using` block; - LX2's trimmed full stop; - the account setting that can force compression on; - what the local schema registry falls back to. - **A5, D4, D5:** - ADR 0018's amendment says "with a response schema" and counts #28 as a fifth Phase 2 library PR; - ADR 0016 adds text cut off before any output; - the plan's risk 4 no longer says a rejection corrects the ratio. **Declined:** - **D10**, refuted: "the chapter's preview" in LX6 states ADR 0018's reason, cited by number, while the rule itself is in the engine's terms (`RunEventChannel.Reasoning`). - **E3, partly:** a wrong-kind provider member other than `max_price` (`"order":"x"`, `"zdr":"true"`) still reaches the writer as the library's `JsonException`, with a .NET type name and `Path: $`. This was so before this branch, and 8b pinned one such row. A follow-up could check each member's kind at its own pointer. ## Second pass A second workflow checked each round-0 fix against its original input and put the defect back to see the new test fail. It also reviewed the tests (the dimension round 0 did not finish) and the fix commit itself. Every new point went to a skeptic. The checks: every round-0 fix held, except A4 and E1, which were only partly resolved. Their gaps are the findings below. The review raised 22 new points: 20 confirmed, 2 partly confirmed (C8, F4), none refuted. All are taken in b360bba. **The schema, round 0's weak spot:** - **K1, M2, F1, T1:** round 0 blamed `$schema` whenever the schema without it built, or failed another way. But without it JsonSchema.Net 8.0.5 builds under its V1 dialect, which refuses unknown keywords. So a draft-07 or 2020-12 schema with an unrelated error was told its dialect cannot be checked, and was advised to name draft-07 or 2020-12. A dialect is now blamed only when a schema of that `$schema` alone does not build. The advice names the dialects by their URIs. - **F2:** a `$schema` that is not an absolute URI (`"draft-07"`) keeps JsonSchema.Net's own sentence, which says it wants one. - **K2, T2, T3:** the start rule built the schema before the strict check, so the strict check's pointed findings ("Strict mode does not support "nullable"; remove it.") were hidden behind one root-level JsonSchema.Net sentence. A plain annotation such as `x-order` was refused too. Now the strict check runs first, and the schema is built only when it finds nothing. A schema without `$schema` is read as draft 2020-12 (`BuildOptions.Dialect`, not the process-wide default), which takes an unknown keyword as an annotation. - **T4:** JsonSchema.Net compares an `enum`'s or a `const`'s numbers as decimals, and throws beyond their range, after the paid call. That is A1's failure through another exception. Such a number in the schema is now refused at start and at LX2. In an answer it is a transient mismatch, and the attempt keeps its facts. - **M1:** ADR 0018 claimed every unreadable schema is refused at start. A1's nested `$id` passes the start. ADR 0018 and 0016 now say so. **The rest:** - **F4:** round 0 skipped `Validate()` after an unknown choice, which hid a null slug. It now runs over the preferences without the choices. - **K3:** `ValidateConfig`'s and `LlmConfigRead`'s summaries say the provider's reading stops early. - **F3:** `Describe` keeps its own rule that a window of 0 is none. - **Tests (C1–C8):** - the facts of every arm before the request; - the finding codes as literals; - three and four failing places; - blank content, refusal and native reason; - two unknown choices and escaped names; - the deltas of two concurrent nodes; - the requested model under nitro; - "copied from a run" comments. - **F5:** sorted usings. ## Out of scope (brief §8) - Starting a run (12), the monitor (13) and the inspector (14). - The palette reading the registry (slice 9). - `TSUMUGI_FAKE_LLM` (D-21, slices 11b and 12). - Agent mode (Phase 4). ## Tests Counted from `dotnet test` (facts plus `InlineData` rows). The base is `main` at 11b466e. | Project | main | this PR | |---|---:|---:| | Kagaku.Flow.OpenRouter.Tests | 430 | **596** (592 run, the 4 live checks skipped) | | Tsumugi.Integration.Tests | 342 | **343** | | Kagaku.Flow.Tests | 1103 | 1103 | | Tsumugi.BlazorAdapter.Tests | 351 | 351 | | Tsumugi.Domain.Tests | 457 | 457 | | Tsumugi.UseCases.Tests | 155 | 155 | | **Total** | **2838** | **3005** (3001 passed, 4 skipped) | New in the package (166): - `LlmOutcomeTests`: 56 (LO1–LO8, LX1–LX3, LX5). - `LlmFactsTests`: 25 (LO9, LO10, LO12). - `LlmExecutionTests`: 18 (LX4, LX6–LX10, LR2). - `LlmRegistrationTests`: 10 (LR1–LR4). - `LlmConfigValidationTests`: +24 (#28's refusals, the provider's choices and order). - `LlmFailureTextTests`: +18 (LO7, LO8). - `LlmStrictSchemaRuleTests`: +14 (the refs budget, unreadable schemas, the dialect, the order of the check and the reading). - `LiveOpenRouterChecks`: +1, the engine's live run (skipped without a key). New in Tsumugi: `ProviderCompositionTests.The_llm_node_type_is_registered_with_its_rule`. The core's tests move `RecordingLogger` to `tests/Shared` and keep their count. ## Coverage Coverlet with branch coverage, on the package's tests. **100% of lines and branches** on every file this PR changes in the package: | File | Lines | Branches | |---|---:|---:| | `Llm/LlmConfig.cs` | 397/397 | 246/246 | | `Llm/LlmExecutor.cs` | 74/74 | 28/28 | | `Llm/LlmSchemaCheck.cs` | 71/71 | 41/41 | | `Llm/LlmFacts.cs` | 33/33 | 51/51 | | `Llm/LlmAnswer.cs` | 35/35 | 41/41 | | `Llm/LlmFailureText.cs` | 39/39 | 33/33 | | `Llm/LlmStrictSchemaRule.cs` | 38/38 | 16/16 | | `Llm/LlmDescriptor.cs` | 130/130 | 44/44 | | `LlmNodeServiceCollectionExtensions.cs` | 8/8 | — | ## Mutation probes Each probe is a single edit, built and tested on its own, one at a time. - **The brief's 8c probes (§12: 44–56, 68–71, 78) and my own (m1–m12):** 46 run. - 39 were killed at once. - 4 failed to compile first and were rewritten, then killed. - m10 (a blank reasoning kept) survived until the blank-reasoning test was added. - m6 (the root result skipped in the mismatch list) found dead code, which is removed. - m12 is **equivalent**: reading the catalog by the id as written instead of `CatalogId`. LC4 keeps routing suffixes out of the model, so the two ids are always equal. - **Round 0:** 16 more, one for each fix, all killed by the new tests. They include A1's uncaught ref, A2's arm, A4's build at start, A6's zero window, E2 and E4, B1's swallowed cancel, both of B2's tokens, B3's two guards, B4's empty host, and A3's received facts. - **Second pass:** 11 more, all killed. They cover: - the dialect decision and the URI check; - the 2020-12 reading and the strict-first order; - T4 on the schema side, on the answer side, and a number that fits; - F4's validation, F3's zero window, C7's wire id, and C1's empty request. - **The brief's equivalent mutants:** LX8's "catch the cancel" is no longer equivalent. B1's test kills it through the engine's log. LG10 (8a) and LB5 (8b) were settled in their PRs. - **Not run:** two of the brief's probes, 69b (publish the deltas after the executor returns) and 70d (sort `DetailsJson`'s members). ## Browser pass Slice 9 is not merged, so the playground still reads `SampleGraph`, and nothing visible changes (brief §16.2, decision 40). 8c does change the host's composition, so I ran the Release build of b360bba, this PR's head (the footer shows the build). It ran with `ASPNETCORE_ENVIRONMENT=Development`, so scope validation and `ValidateOnBuild` run at startup, and with the gateway pointed at slice 7's local stub. - **Startup:** the server started, so the composition with `AddKagakuFlowLlmNode` validates. - **`/graphs/playground`:** Context → Writer (`LLM`, accent) → Commit, as before. There were no console errors or warnings. Screenshots were taken light and dark. ## Live checks Run on 2026-09-26 with your one-day debug key; all four passed, about $0.02 in total. The results are in [this comment](https://git.kagaku.eu/TeamAI/Tsumugi/pulls/40#issuecomment-9851). - **The engine's run** succeeded in one attempt, served by Amazon Bedrock. - **D-27:** both cache markers read back. - **D-6:** OpenRouter's own length rejection carries no `error_type`. It reads as a refused request, still permanent, and afb83fc records this. Still yours from before: Continue's model id. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
LlmExecutor runs one attempt of an llm node. Before anything is sent, a
config that does not read, a schema that cannot be inlined or built, a model
missing from the catalog, and, for a node with a response schema, a model
whose entry does not list structured_outputs fail the row at once; a catalog
that cannot load is transient. The install-wide defaults are read once per
attempt and never cached. The request streams through the gateway: reasoning
deltas go on the reasoning channel before the text of their chunk, a refusal
is never published, and every delta is out before the executor returns.

The outcome. A text answer is read by its finish: a refusal and a content
filter are permanent; no message, a provider error, a tool call and no text
are transient; a cut-off at max_tokens succeeds, its finish reason recorded.
A structured answer is ParseJson's, in the library's words and by its kind,
except a tool call, which takes the node's sentence; its value is then
checked with JsonSchema.Net, each schema built into a registry of its own,
and a mismatch is transient, naming its first three places. A failed call is
transient or permanent by the library's kind, in a sentence per kind; a
proxy's page never reaches the ledger, and a length rejection names the
model, its window and the characters sent.

The facts. Every arm after the request carries the body as it went over the
wire, the details, the requested model and the characters sent; with an
answer, the served model, provider, tier in OpenRouter's spelling, finish
reason, generation, reasoning and token counts; and the attempt's cost,
failures included.

AddKagakuFlowLlmNode registers the descriptor, executor and strict-schema
rule as enumerable singletons, once however often it is called, and a
template host unless one is registered; Tsumugi calls it beside the gateway.
The package gets a README of its rules (LG, LC, LS, LB, LD, LQ, LX, LO, LR).

ADR 0016 and 0018: a refusal, a content filter and structured output cut off
at max_tokens fail the row at once, since a retry repeats them; a node with
a response schema on a model without structured_outputs fails its run at
once. ADR 0017: a length rejection carries no structured token count, so the
ratio learns from completed rows only. The settings story's routing line is
ticked.

The engine's live run joins the live checks: a one-node graph on a real
model, through the engine, with the library's retries.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
OpenRouter.Net #28 refuses what it used to drop: an unknown member of the
preferences or of max_price, a price ceiling that is not a decimal, an
unknown or wrong-kind member of a sort or percentile object, a cutoff that
is not finite; and Validate() now refuses an enum value it does not know and
a null slug.

LC9 names an unknown member inside max_price as it names one of the
preferences, at its pointer and in the writer's words. The library refuses
an unknown member too, in a developer's words, so after a name problem the
preferences are not read: each typo is said once. What else the library
refuses arrives as "The provider preferences do not read: …" with its own
sentence, and Validate()'s messages as before. A ValidateConfig row pins
each refusal.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Before opening, a review workflow (engine, docs, config, and six known
correctness points; each finding put to a skeptic) confirmed 20 points,
partly confirmed 3 (E1, E3, E4) and refuted one (D10). The tests reviewer
did not finish; the second pass carries that dimension. E3's wrong-kind
provider members other than max_price keep the library's sentence, as they
did before this branch: a follow-up.

The executor:
- A1: JsonSchema.Net resolves a ref only when the evaluation reaches it, so a
  ref it cannot resolve (a nested $id re-basing "#/$defs/…") threw after the
  paid call, three times, with no cost recorded. LlmSchemaCheck.Check now
  returns LX2's "The response schema cannot be read: …" as a permanent
  failure, and the attempt keeps its facts.
- A2: a text answer cut off at max_tokens before any text (reasoning spent
  the budget) is permanent, with the advice to raise max_tokens or lower the
  effort, instead of three paid retries saying "no text".
- A4: the strict-schema rule builds the schema as LX2 does, so a schema
  JsonSchema.Net cannot read is refused at start (openrouter.schema-not-
  readable). A root "$schema" it does not know is said in the writer's words
  when it is what stopped the build.
- A6: a length rejection names the catalog's window as the catalog's (an
  endpoint may serve less), and a window of 0 is none.

The config (LC9, LC12):
- E2: a data collection policy, precision, sort or partition the library
  does not know is refused at its own pointer with the spellings it takes,
  instead of the library's "… is Unknown, which has no wire spelling. Use
  Quantization.Unknown …".
- E4, D1: the provider's names are checked in one pass, in document order,
  a max_price's members where it stands; a max_price that is not an object is
  named at /provider/max_price. LC12 and Read's summary say the order the code
  keeps; E1: they also say where the provider's reading stops early.
- D3: rows for the library's remaining refusals (a wrong-kind or unknown
  sort or percentile member, an object that sets nothing).

The tests:
- B1: the cancel test asserts the engine's log line, which an executor that
  swallowed the cancel changes; the world records the engine's log
  (RecordingLogger moves to tests/Shared, in its own namespace).
- B2: a shutdown while the catalog or the defaults load leaves the row
  running and sends nothing.
- B3: an empty content or reasoning fragment publishes nothing.
- B4: without the core, the template host the node registers extends each
  render with the registered extensions.
- A3: a broken-off attempt's served model, provider and generation come from
  what the stream delivered.
- The probe fixes: LlmSchemaCheck's dead Prepend removed, a blank reasoning
  not recorded, the scope test resolving the registry's executor.

The docs:
- D2, D6, D7, D8: LS2 lists the refs-followed budget; LX6 names Publish; LG2
  and LG4 name ClientsFor and the using block; LX2 says how its message is
  trimmed; LQ13 names the account setting that can force compression; LO6
  says what the local registry falls back to.
- A5, D4, D5: ADR 0018's amendment says "with a response schema" and counts
  a fifth Phase 2 library PR (#28); ADR 0016 adds text cut off before any
  output; the plan's risk 4 no longer says a rejection corrects the ratio.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(flow): second pass — a dialect is blamed only when unknown, the strict check speaks first, numbers the check cannot compare
Some checks failed
CI / build (pull_request) Has been cancelled
CI / test (pull_request) Has been cancelled
CI / editor-browsers (pull_request) Has been cancelled
b360bbaec8
The second pass checked round 0's fixes, reviewed the tests (the dimension
round 0 did not finish) and the fix commit, and put each new point to a
skeptic: 20 confirmed, 2 partly confirmed (C8, F4), none refuted. All are
taken here.

The schema:
- K1, M2, F1, T1: round 0 blamed "$schema" whenever the schema without it
  built or failed another way, but without it JsonSchema.Net 8.0.5 builds
  under V1, which refuses unknown keywords; a draft-07 or 2020-12 schema was
  told its dialect cannot be checked. A dialect is now blamed only when a
  schema of that "$schema" alone does not build, and the advice names the
  dialects by their URIs. F2: a "$schema" that is not an absolute URI keeps
  JsonSchema.Net's sentence, which says so.
- K2, T2, T3: the strict check runs first and the schema is built only when
  it finds nothing, so its pointed findings are never hidden. A schema
  without "$schema" is read as draft 2020-12 (BuildOptions.Dialect, not the
  process-wide default), so an annotation such as x-order is not refused.
- T4: JsonSchema.Net compares an enum's or a const's numbers as decimals and
  throws beyond their range, after the paid call. Such a number in the
  schema is refused at start and at LX2; in an answer it is a transient
  mismatch, and the attempt keeps its facts.
- M1: ADR 0018 and 0016 say a schema the validator cannot build is refused
  at start, and one whose ref it resolves only when an answer reaches it
  fails the row after that paid attempt.

The config:
- F4: Validate() runs after an unknown choice too, over the preferences
  without the choices, so a null slug is not hidden. K3: ValidateConfig's
  and LlmConfigRead's summaries say the provider's reading stops early.

The rest:
- F3: Describe keeps its own rule, a window of 0 is none.
- Tests (C1-C8): the facts of every arm before the request; the finding
  codes as literals; three and four failing places; blank content, refusal
  and native reason; two unknown choices, escaped names; the deltas of two
  concurrent nodes; the requested model under nitro; "copied from a run"
  comments. F5: sorted usings.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
bjoern referenced this pull request from a commit 2026-09-26 10:33:13 +02:00
docs: slice 8c's PR is #40
Some checks failed
CI / build (pull_request) Successful in 36s
CI / editor-browsers (pull_request) Successful in 40s
CI / test (pull_request) Failing after 1m9s
3cbacc8c5a
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
docs(flow): OpenRouter's own length rejection carries no error type — a refused request, still permanent
All checks were successful
CI / build (pull_request) Successful in 34s
CI / editor-browsers (pull_request) Successful in 40s
CI / test (pull_request) Successful in 1m10s
afb83fca02
The live check on this PR (openai/gpt-3.5-turbo, 33,000 tokens against
16,385) answered a 400 without error_type, which the library reads as
InvalidRequest: permanent, with OpenRouter's sentence naming the
endpoint's maximum and the tokens requested. ADR 0017 and README LO8 now
say so; LO8's own sentence stands for a rejection that carries the type.

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

Live checks, run 2026-09-26 on this PR's head with a one-day debug key. All four passed, at about $0.02 in total.

The engine's live run (A_real_llm_node_runs_through_the_engine):

status Succeeded, attempts 1, cost 0.000086, error (none)
output {"text":"1. Blue\n2. Red\n3. Green"}
deltas 2, served anthropic/claude-haiku-4.5 by Amazon Bedrock, tier default
details {"model":"anthropic/claude-haiku-4.5","routing":"balanced","routingFrom":"defaults","serviceTier":"auto","serviceTierFrom":"defaults","zeroDataRetention":false,"effort":"default","effortSent":false,"samplingSent":[],"samplingDropped":[],"cacheMarker":null,"omittedMessages":[]}

D-27, re-run against OpenRouter.Net b002f5e: both cache markers read back from Anthropic's cache.

Marker Call 1 Call 2
message-level writes 5788 tokens, $0.007272 reads 5788 cached tokens, $0.0006108
part-level writes 5788 tokens, $0.007267 reads 5788 cached tokens, $0.0006108

D-6, the context-length capture you still owed:

== context length on openai/gpt-3.5-turbo, streamed through the gateway ==
failure: InvalidRequest, status 400: This endpoint's maximum context length is 16385 tokens. However, you requested about 33016 tokens (33000 of text input, 16 in the output). Please reduce the length of either one, or use the context-compression plugin to compress your prompt automatically.
raw error type: (none), provider code: (none), code: 400
metadata: {"provider_name":null}
== the same shape over openai/gpt-3.5-turbo-0613's 4,095 tokens, compression left on ==
status: 200, prompt_tokens 944 (the prompt was cut to fit, as LQ13 says)

What D-6 means for this PR: OpenRouter's own rejection carries no error_type, so the library reads it as InvalidRequest, not ContextLength. That is decision 5's fallback:

  • the row still fails permanently after one attempt, as ADR 0017 requires;
  • the error is "OpenRouter refused the request: This endpoint's maximum context length is 16385 tokens. However, you requested about 33016 tokens …", which names the overflow;
  • LO8's own sentence (the model, the characters sent, the remedy) does not fire for OpenRouter's rejection. It fires only for one that carries the type.

A_length_rejection_without_its_error_type_is_still_permanent already pins this shape. afb83fc records the verdict in ADR 0017 and README LO8.

Two points for you:

  • OpenRouter's sentence ends with "or use the context-compression plugin". The node deliberately turns that plugin off (LQ13, your #35 decision), so the advice is one the writer cannot follow.
  • Getting LO8's wording back needs a library change: reading OpenRouter's own 400 as ContextLength. It has provider_name: null and a stable "This endpoint's maximum context length is" sentence. Brief decision 5 rejected classifying by prose, as provider-specific and untestable against a contract. This prose is OpenRouter's own, though, not a provider's.

I'd leave both for a library follow-up rather than grow this PR. Say if you want it sooner.

**Live checks, run 2026-09-26 on this PR's head with a one-day debug key.** All four passed, at about $0.02 in total. **The engine's live run** (`A_real_llm_node_runs_through_the_engine`): ``` status Succeeded, attempts 1, cost 0.000086, error (none) output {"text":"1. Blue\n2. Red\n3. Green"} deltas 2, served anthropic/claude-haiku-4.5 by Amazon Bedrock, tier default details {"model":"anthropic/claude-haiku-4.5","routing":"balanced","routingFrom":"defaults","serviceTier":"auto","serviceTierFrom":"defaults","zeroDataRetention":false,"effort":"default","effortSent":false,"samplingSent":[],"samplingDropped":[],"cacheMarker":null,"omittedMessages":[]} ``` **D-27, re-run against OpenRouter.Net b002f5e:** both cache markers read back from Anthropic's cache. | Marker | Call 1 | Call 2 | |---|---|---| | message-level | writes 5788 tokens, $0.007272 | reads 5788 cached tokens, $0.0006108 | | part-level | writes 5788 tokens, $0.007267 | reads 5788 cached tokens, $0.0006108 | **D-6, the context-length capture you still owed:** ``` == context length on openai/gpt-3.5-turbo, streamed through the gateway == failure: InvalidRequest, status 400: This endpoint's maximum context length is 16385 tokens. However, you requested about 33016 tokens (33000 of text input, 16 in the output). Please reduce the length of either one, or use the context-compression plugin to compress your prompt automatically. raw error type: (none), provider code: (none), code: 400 metadata: {"provider_name":null} == the same shape over openai/gpt-3.5-turbo-0613's 4,095 tokens, compression left on == status: 200, prompt_tokens 944 (the prompt was cut to fit, as LQ13 says) ``` **What D-6 means for this PR:** OpenRouter's own rejection carries **no `error_type`**, so the library reads it as `InvalidRequest`, not `ContextLength`. That is decision 5's fallback: - the row still fails permanently after one attempt, as ADR 0017 requires; - the error is "OpenRouter refused the request: This endpoint's maximum context length is 16385 tokens. However, you requested about 33016 tokens …", which names the overflow; - LO8's own sentence (the model, the characters sent, the remedy) does not fire for OpenRouter's rejection. It fires only for one that carries the type. `A_length_rejection_without_its_error_type_is_still_permanent` already pins this shape. afb83fc records the verdict in ADR 0017 and README LO8. Two points for you: - **OpenRouter's sentence ends with "or use the context-compression plugin".** The node deliberately turns that plugin off (LQ13, your #35 decision), so the advice is one the writer cannot follow. - **Getting LO8's wording back needs a library change:** reading OpenRouter's own 400 as `ContextLength`. It has `provider_name: null` and a stable "This endpoint's maximum context length is" sentence. Brief decision 5 rejected classifying by prose, as provider-specific and untestable against a contract. This prose is OpenRouter's own, though, not a provider's. I'd leave both for a library follow-up rather than grow this PR. Say if you want it sooner.
Member

🔮 fufu~ Jibril reviewed your code!

The last of the three llm-node slices — the one where the node actually breathes~ I read all 38 files of the diff, every changed file's full content, and traced every contract the executor leans on: the engine's (INodeExecutor, NodeOutcome, NodeFacts, NodeExecution's delta gate), the gateway's from 8a (ILlmGateway throws only on the caller's cancellation — so a cancel settles nothing, exactly as LX8 claims), and the library's at b002f5e. Oh, and I ran your suite and then tried to break your code myself. ♡

Verdict: ✅ Looks good to me~

What I verified locally (CI has not landed for 3cbacc8 yet):

  • Build: Release, 0 warnings / 0 errors under TreatWarningsAsErrors, both submodules at their pins (Kagaku.UI d512741, OpenRouter.Net b002f5e).
  • Full suite, matching your table to the digit: OpenRouter 592 passed + 4 live-skipped = 596 · Integration 343 · Flow 1103 · BlazorAdapter 351 · Domain 457 · UseCases 155 — 3001 passed, 4 skipped, 3005 total.
  • My own mutation probes, each built --no-incremental, run, restored, tree left pristine (verified git status clean after each):
    1. Swapped Publish to text-before-reasoning → exactly 1 of 18 execution tests red (the chunk-order pin). Killed on its own pin~
    2. The brief's LX8 "catch the cancel" equivalent mutant (catch OperationCanceledException, return Transient) → 4 red, the cancel + both shutdown-window tests — B1's engine-log-line assertion does kill it through the log, as the body claims. No longer equivalent. ♪
    3. LlmFacts.From ignoring completion.Received on a broken stream → exactly 1 of 25 facts tests red (A3's broken-off-attempt pin).
  • The library contracts the new config code leans on (this is where I expected blood, fufu~): DataCollection/Sort are nullable and Unknown is each enum's zero member, so every provider.GetProperty(...) in Unknown() is guarded by a condition the member's presence implies — and the ordering test with sort before data_collection proves document order survives. Nulling the three choices before Validate() keeps the null-slug and non-finite checks. Spellings<T>() goes through the enum's own JsonConverterFactory, so [EnumMember] overrides serialize. ProviderSortConfig's converter refuses unknown members itself. ParseJson's IsTransient (Malformed yes, ContentPolicy/ContextLength no) makes decision 11's permanent arms exactly right.
  • Submodule purity: 15a1f28..b002f5e is precisely #28's four-commit chain (2eb6f82 → 5d037ff → c2f0632 → merge), nothing else.
  • Docs: the 0016/0017/0018 amendments and the plan's risk-4 retraction each state what the code now does — I traced every amended sentence to a branch or a test that pins it.

💡 Little ideas (non-blocking)~

  1. The two unrun brief probes (69b, 70d) are honestly disclosed — 69b is structurally excluded by LG5's "callback only before CompleteAsync returns" and 70d is cosmetic — but when a probe is skipped for a reason rather than impossibility, a one-line note in the body keeps the ledger clean.
  2. E3's declined half (wrong-kind provider members other than max_price surfacing as the library's raw JsonException with a .NET type name) — agreed it predates this branch and 8b pinned one row; the per-member kind check you sketch would make a tidy library-side follow-up someday.

✅ What I liked~

  • The cancel/shutdown test trio (mid-stream, catalog-load, defaults-load) with the parked-gate harness — the exact windows where a lesser PR would have settled a row it shouldn't. The engine-log assertion to kill the LX8 equivalent mutant is genuinely clever. Oh? Oh! This is wonderful~
  • LlmSchemaCheck's dialect attribution — blaming $schema only when a bare schema of that dialect alone fails to build, with the URIs named — is the rare error message that teaches the writer rather than the developer.
  • The SeedAsync trick for graphs a start would refuse: testing executor arms the validation can't reach, without weakening the rule. ♪
  • Facts on every arm, failures included — the ledger-back design honored to the last decimal (0.0001m × 3 asserted!).

The live check is still owed to bjoern (a fraction of a cent, the command is in the body) — the four skipped tests are exactly that debt. Everything a sandbox can prove, this PR proves.


Automated review by Jibril · 2026-09-26
CI/CD: absent for head 3cbacc8 (PR 10 min old, no bot comment yet) · Local checks: full build + 3005-test suite + 3/3 mutation probes killed

## 🔮 fufu~ Jibril reviewed your code! The last of the three llm-node slices — the one where the node actually *breathes*~ I read all 38 files of the diff, every changed file's full content, and traced every contract the executor leans on: the engine's (`INodeExecutor`, `NodeOutcome`, `NodeFacts`, `NodeExecution`'s delta gate), the gateway's from 8a (`ILlmGateway` throws only on the caller's cancellation — so a cancel settles nothing, exactly as LX8 claims), and the library's at b002f5e. Oh, and I ran your suite and then tried to break your code myself. ♡ ### Verdict: ✅ Looks good to me~ **What I verified locally** (CI has not landed for 3cbacc8 yet): - **Build**: Release, 0 warnings / 0 errors under `TreatWarningsAsErrors`, both submodules at their pins (Kagaku.UI d512741, OpenRouter.Net b002f5e). - **Full suite, matching your table to the digit**: OpenRouter 592 passed + 4 live-skipped = 596 · Integration 343 · Flow 1103 · BlazorAdapter 351 · Domain 457 · UseCases 155 — **3001 passed, 4 skipped, 3005 total**. - **My own mutation probes, each built `--no-incremental`, run, restored, tree left pristine** (verified `git status` clean after each): 1. Swapped `Publish` to text-before-reasoning → exactly 1 of 18 execution tests red (the chunk-order pin). Killed on its own pin~ 2. The brief's LX8 "catch the cancel" equivalent mutant (catch `OperationCanceledException`, return Transient) → 4 red, the cancel + both shutdown-window tests — B1's engine-log-line assertion does kill it through the log, as the body claims. No longer equivalent. ♪ 3. `LlmFacts.From` ignoring `completion.Received` on a broken stream → exactly 1 of 25 facts tests red (A3's broken-off-attempt pin). - **The library contracts the new config code leans on** (this is where I expected blood, fufu~): `DataCollection`/`Sort` are nullable and `Unknown` is each enum's zero member, so every `provider.GetProperty(...)` in `Unknown()` is guarded by a condition the member's presence implies — and the ordering test with `sort` before `data_collection` proves document order survives. Nulling the three choices before `Validate()` keeps the null-slug and non-finite checks. `Spellings<T>()` goes through the enum's own `JsonConverterFactory`, so `[EnumMember]` overrides serialize. `ProviderSortConfig`'s converter refuses unknown members itself. `ParseJson`'s `IsTransient` (Malformed yes, ContentPolicy/ContextLength no) makes decision 11's permanent arms exactly right. - **Submodule purity**: 15a1f28..b002f5e is precisely #28's four-commit chain (2eb6f82 → 5d037ff → c2f0632 → merge), nothing else. - **Docs**: the 0016/0017/0018 amendments and the plan's risk-4 retraction each state what the code now does — I traced every amended sentence to a branch or a test that pins it. #### 💡 Little ideas (non-blocking)~ 1. The two unrun brief probes (69b, 70d) are honestly disclosed — 69b is structurally excluded by LG5's "callback only before `CompleteAsync` returns" and 70d is cosmetic — but when a probe is skipped for a *reason* rather than impossibility, a one-line note in the body keeps the ledger clean. 2. E3's declined half (wrong-kind provider members other than `max_price` surfacing as the library's raw `JsonException` with a .NET type name) — agreed it predates this branch and 8b pinned one row; the per-member kind check you sketch would make a tidy library-side follow-up someday. #### ✅ What I liked~ - The **cancel/shutdown test trio** (mid-stream, catalog-load, defaults-load) with the parked-gate harness — the exact windows where a lesser PR would have settled a row it shouldn't. The engine-log assertion to kill the LX8 equivalent mutant is genuinely clever. Oh? Oh! This is wonderful~ - **`LlmSchemaCheck`'s dialect attribution** — blaming `$schema` only when a bare schema of that dialect alone fails to build, with the URIs named — is the rare error message that teaches the writer rather than the developer. - The `SeedAsync` trick for graphs a start would refuse: testing executor arms the validation can't reach, without weakening the rule. ♪ - Facts on every arm, failures included — the ledger-back design honored to the last decimal (`0.0001m × 3` asserted!). The live check is still owed to bjoern (a fraction of a cent, the command is in the body) — the four skipped tests are exactly that debt. Everything a sandbox can prove, this PR proves. --- *Automated review by Jibril · 2026-09-26* *CI/CD: absent for head 3cbacc8 (PR 10 min old, no bot comment yet) · Local checks: full build + 3005-test suite + 3/3 mutation probes killed*
bjoern merged commit 64d6faf500 into main 2026-09-26 10:48:18 +02:00
bjoern deleted branch feat/llm-executor 2026-09-26 10:48:19 +02:00

🔄 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: afb83fc · Generated: 2026-09-26 08:52:54 UTC · Revision: #1

Summary

Summary
Generated on: 09/26/2026 - 08:52:54
Coverage date: 09/26/2026 - 08:52:36 - 09/26/2026 - 08:52:51
Parser: MultiReport (6x Cobertura)
Assemblies: 9
Classes: 461
Files: 434
Line coverage: 97.9% (14021 of 14317)
Covered lines: 14021
Uncovered lines: 296
Coverable lines: 14317
Total lines: 30973
Branch coverage: 94.3% (5452 of 5781)
Covered branches: 5452
Total branches: 5781
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.LlmCompletion 100%
Kagaku.Flow.OpenRouter.Gateway.OpenRouterGateway 100% 100%
Kagaku.Flow.OpenRouter.Gateway.OpenRouterGatewayOptions 100%
Kagaku.Flow.OpenRouter.Gateway.OpenRouterKeyLookup 100% 100%
Kagaku.Flow.OpenRouter.Gateway.SentRequestCapture 100% 100%
Kagaku.Flow.OpenRouter.Gateway.SentRequestCaptureHandler 100% 100%
Kagaku.Flow.OpenRouter.Llm.LlmAnswer 100% 100%
Kagaku.Flow.OpenRouter.Llm.LlmConfig 100% 100%
Kagaku.Flow.OpenRouter.Llm.LlmConfigRead 100%
Kagaku.Flow.OpenRouter.Llm.LlmDescriptor 100% 100%
Kagaku.Flow.OpenRouter.Llm.LlmExecutor 100% 100%
Kagaku.Flow.OpenRouter.Llm.LlmFacts 100% 100%
Kagaku.Flow.OpenRouter.Llm.LlmFailureText 100% 100%
Kagaku.Flow.OpenRouter.Llm.LlmMessage 100%
Kagaku.Flow.OpenRouter.Llm.LlmRequestBuilder 100% 100%
Kagaku.Flow.OpenRouter.Llm.LlmRequestPlan 100%
Kagaku.Flow.OpenRouter.Llm.LlmRoutingDefaults 100% 100%
Kagaku.Flow.OpenRouter.Llm.LlmSampling 100%
Kagaku.Flow.OpenRouter.Llm.LlmSchemaCheck 100% 100%
Kagaku.Flow.OpenRouter.Llm.LlmStrictSchemaRule 100% 100%
Kagaku.Flow.OpenRouter.Llm.LlmVocabulary 100% 100%
Kagaku.Flow.OpenRouter.LlmNodeServiceCollectionExtensions 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.4%
Name Line Branch
Tsumugi.BlazorAdapter 96.4% 92.6%
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 95.1% 88.8%
Tsumugi.BlazorAdapter.Documents.DocumentImageRefused 100%
Tsumugi.BlazorAdapter.Documents.DocumentImageSet 100%
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% 96.6%
Tsumugi.BlazorAdapter.Documents.FieldsChanges 100% 100%
Tsumugi.BlazorAdapter.Documents.FieldShapes 90% 87.5%
Tsumugi.BlazorAdapter.Documents.ImageUpload 100%
Tsumugi.BlazorAdapter.Documents.ImageUploadRefusal 100%
Tsumugi.BlazorAdapter.Documents.ImpactText 100% 94.2%
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.SetDocumentImageRequested 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 - 96%
Name Line Branch
Tsumugi.Domain 96% 94.6%
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 97% 95.3%
Tsumugi.Domain.Documents.FieldMessages 100% 100%
Tsumugi.Domain.Documents.FieldNames 100%
Tsumugi.Domain.Documents.FieldsChange 100%
Tsumugi.Domain.Documents.FieldShape 95.2% 87.5%
Tsumugi.Domain.Documents.FieldsPlan 100%
Tsumugi.Domain.Documents.FieldsPlanner 99.2% 98.5%
Tsumugi.Domain.Documents.FieldValue 100%
Tsumugi.Domain.Documents.FieldValues 95.4% 97.7%
Tsumugi.Domain.Documents.ImageFiles 100% 100%
Tsumugi.Domain.Documents.ImageValue 100%
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.StoryImage 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.InlineMarkup 100% 100%
Tsumugi.Domain.Stories.MarkupRun 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.7%
Name Line Branch
Tsumugi.Infrastructure 97.7% 85.9%
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.StoryImageConfiguration 100%
Tsumugi.Infrastructure.Persistence.Configurations.StorySnapshotConfiguratio
n
100%
Tsumugi.Infrastructure.Persistence.Converters.CanonicalJsonComparer 100%
Tsumugi.Infrastructure.Persistence.Converters.CategoryFieldsConverter 97.6% 94.4%
Tsumugi.Infrastructure.Persistence.Converters.FieldValuesConverter 96.9% 93.7%
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.StoryImages 100%
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.3% 75%
Tsumugi.Infrastructure.Persistence.Stores.EfImageStore 100%
Tsumugi.Infrastructure.Persistence.Stores.EfSegmentStore 95% 84.6%
Tsumugi.Infrastructure.Persistence.Stores.EfStoryStore 98.7% 90%
Tsumugi.Infrastructure.Persistence.TsumugiDbContext 85.5% 89.2%
Tsumugi.Infrastructure.Persistence.TsumugiDbContextFactory 0%
Tsumugi.Infrastructure.Provider.LlmDefaultsSource 100%
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.3%
Name Line Branch
Tsumugi.UseCases 98.3% 93.9%
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.2% 71.4%
Tsumugi.UseCases.Diagnostics.SeedPictures 100%
Tsumugi.UseCases.Documents.ChangeCategoryFields 100% 83.3%
Tsumugi.UseCases.Documents.CreateCategory 100% 100%
Tsumugi.UseCases.Documents.CreateDocument 100% 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.GetStoryImage 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.SetDocumentImage 100% 93.7%
Tsumugi.UseCases.Documents.StoryImageFile 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.ModelOutput 98.2% 96.6%
Tsumugi.UseCases.Segments.ModelParagraph 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:** `afb83fc` · **Generated:** 2026-09-26 08:52:54 UTC · **Revision:** #1 # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 09/26/2026 - 08:52:54 | | Coverage date: | 09/26/2026 - 08:52:36 - 09/26/2026 - 08:52:51 | | Parser: | MultiReport (6x Cobertura) | | Assemblies: | 9 | | Classes: | 461 | | Files: | 434 | | **Line coverage:** | 97.9% (14021 of 14317) | | Covered lines: | 14021 | | Uncovered lines: | 296 | | Coverable lines: | 14317 | | Total lines: | 30973 | | **Branch coverage:** | 94.3% (5452 of 5781) | | Covered branches: | 5452 | | Total branches: | 5781 | | **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.LlmCompletion|100%|| |Kagaku.Flow.OpenRouter.Gateway.OpenRouterGateway|100%|100%| |Kagaku.Flow.OpenRouter.Gateway.OpenRouterGatewayOptions|100%|| |Kagaku.Flow.OpenRouter.Gateway.OpenRouterKeyLookup|100%|100%| |Kagaku.Flow.OpenRouter.Gateway.SentRequestCapture|100%|100%| |Kagaku.Flow.OpenRouter.Gateway.SentRequestCaptureHandler|100%|100%| |Kagaku.Flow.OpenRouter.Llm.LlmAnswer|100%|100%| |Kagaku.Flow.OpenRouter.Llm.LlmConfig|100%|100%| |Kagaku.Flow.OpenRouter.Llm.LlmConfigRead|100%|| |Kagaku.Flow.OpenRouter.Llm.LlmDescriptor|100%|100%| |Kagaku.Flow.OpenRouter.Llm.LlmExecutor|100%|100%| |Kagaku.Flow.OpenRouter.Llm.LlmFacts|100%|100%| |Kagaku.Flow.OpenRouter.Llm.LlmFailureText|100%|100%| |Kagaku.Flow.OpenRouter.Llm.LlmMessage|100%|| |Kagaku.Flow.OpenRouter.Llm.LlmRequestBuilder|100%|100%| |Kagaku.Flow.OpenRouter.Llm.LlmRequestPlan|100%|| |Kagaku.Flow.OpenRouter.Llm.LlmRoutingDefaults|100%|100%| |Kagaku.Flow.OpenRouter.Llm.LlmSampling|100%|| |Kagaku.Flow.OpenRouter.Llm.LlmSchemaCheck|100%|100%| |Kagaku.Flow.OpenRouter.Llm.LlmStrictSchemaRule|100%|100%| |Kagaku.Flow.OpenRouter.Llm.LlmVocabulary|100%|100%| |Kagaku.Flow.OpenRouter.LlmNodeServiceCollectionExtensions|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.4%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Tsumugi.BlazorAdapter**|**96.4%**|**92.6%**| |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|95.1%|88.8%| |Tsumugi.BlazorAdapter.Documents.DocumentImageRefused|100%|| |Tsumugi.BlazorAdapter.Documents.DocumentImageSet|100%|| |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%|96.6%| |Tsumugi.BlazorAdapter.Documents.FieldsChanges|100%|100%| |Tsumugi.BlazorAdapter.Documents.FieldShapes|90%|87.5%| |Tsumugi.BlazorAdapter.Documents.ImageUpload|100%|| |Tsumugi.BlazorAdapter.Documents.ImageUploadRefusal|100%|| |Tsumugi.BlazorAdapter.Documents.ImpactText|100%|94.2%| |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.SetDocumentImageRequested|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 - 96%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Tsumugi.Domain**|**96%**|**94.6%**| |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|97%|95.3%| |Tsumugi.Domain.Documents.FieldMessages|100%|100%| |Tsumugi.Domain.Documents.FieldNames|100%|| |Tsumugi.Domain.Documents.FieldsChange|100%|| |Tsumugi.Domain.Documents.FieldShape|95.2%|87.5%| |Tsumugi.Domain.Documents.FieldsPlan|100%|| |Tsumugi.Domain.Documents.FieldsPlanner|99.2%|98.5%| |Tsumugi.Domain.Documents.FieldValue|100%|| |Tsumugi.Domain.Documents.FieldValues|95.4%|97.7%| |Tsumugi.Domain.Documents.ImageFiles|100%|100%| |Tsumugi.Domain.Documents.ImageValue|100%|| |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.StoryImage|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.InlineMarkup|100%|100%| |Tsumugi.Domain.Stories.MarkupRun|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.7%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Tsumugi.Infrastructure**|**97.7%**|**85.9%**| |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.StoryImageConfiguration|100%|| |Tsumugi.Infrastructure.Persistence.Configurations.StorySnapshotConfiguratio<br/>n|100%|| |Tsumugi.Infrastructure.Persistence.Converters.CanonicalJsonComparer|100%|| |Tsumugi.Infrastructure.Persistence.Converters.CategoryFieldsConverter|97.6%|94.4%| |Tsumugi.Infrastructure.Persistence.Converters.FieldValuesConverter|96.9%|93.7%| |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.StoryImages|100%|| |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.3%|75%| |Tsumugi.Infrastructure.Persistence.Stores.EfImageStore|100%|| |Tsumugi.Infrastructure.Persistence.Stores.EfSegmentStore|95%|84.6%| |Tsumugi.Infrastructure.Persistence.Stores.EfStoryStore|98.7%|90%| |Tsumugi.Infrastructure.Persistence.TsumugiDbContext|85.5%|89.2%| |Tsumugi.Infrastructure.Persistence.TsumugiDbContextFactory|0%|| |Tsumugi.Infrastructure.Provider.LlmDefaultsSource|100%|| |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.3%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Tsumugi.UseCases**|**98.3%**|**93.9%**| |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.2%|71.4%| |Tsumugi.UseCases.Diagnostics.SeedPictures|100%|| |Tsumugi.UseCases.Documents.ChangeCategoryFields|100%|83.3%| |Tsumugi.UseCases.Documents.CreateCategory|100%|100%| |Tsumugi.UseCases.Documents.CreateDocument|100%|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.GetStoryImage|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.SetDocumentImage|100%|93.7%| |Tsumugi.UseCases.Documents.StoryImageFile|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.ModelOutput|98.2%|96.6%| |Tsumugi.UseCases.Segments.ModelParagraph|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>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
4 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!40
No description provided.