feat: story.* in every template and documents rendered (Phase 2 · slice 9a) #50

Merged
bjoern merged 5 commits from feat/story-nodes into main 2026-09-26 15:40:17 +02:00
Member

Slice 9a of Phase 2, the first of three PRs for "the story half" (9a: story.* and the document rendering; 9b: the context node; 9c: the commit node and the registry in the playground). Every template now reads the run's story through story.*, and a document renders the way a model will read it. Nothing runs a graph yet; the llm node (#40) renders its messages with these members once slice 11 starts runs.

Commits:

  • 11e76d7 is the feature.
  • 41bb772 holds the fixes from the review I ran before opening (round 0, below).
  • 604e4e6 holds the second pass over those fixes (below).
  • e058603 writes this PR's number into ADR 0013, 0017 and 0019 and the plan.
  • ba80e28 is review round 1: a title and a field are named whole (Jibril's ⛔).

What

Tsumugi.UseCases/Context:

  • DocumentRendering renders a document in ADR 0017's and 0028's fixed format:

    • under its ## title, the one-line fields holding a value come first, as Name: value lines, in the category's order;
    • then each long text holding a value, as a ### Name section;
    • a category whose only field is long text renders it as a body, as a Body read before fields existed;
    • empty fields and images render nothing (ADR 0029);
    • values are read only through the category's current fields.
      A value's text is FieldDefinition.Display's (172 cm, the option's name, YYYY-MM-DD), the same text the editor's dialogs show.
  • ContextText holds the section headings and separators, which 9b's context node will use too.

  • StoryTemplateModel (an ITemplateModelExtension) adds the story root to every render:

    Member Value
    story.title, story.description, story.categories the story's own fields; the category names
    story.documents(categories) the named categories' documents, rendered, in category order, then title
    story.documents.index(categories) a title — summary line each (the title alone without a summary)
    story.document(title), story.field(title, name) one document without its heading; one value as text
    story.recent(n) the current chapter's last n paragraphs, scene breaks as ***
    story.chapter(i), story.chapters, story.summaries a chapter's text (from 1); the list with summaries; the earlier chapters' summaries
    story.current_chapter index, title, segment_count
    • Names match ignoring case after trimming; one that matches nothing is skipped (ADR 0013).
    • Each member is a function, loaded at most once per render through the render's own scope.
    • Segment text is emitted as stored, as markup since #38 (ADR 0017, 0030).
  • StoryScope reads the story from the run's owner (ADR 0016: the owner is the story). The current chapter is the trigger's targetChapterId, which slice 11b will write; without one, or once that chapter is gone, it is the story's last chapter (ADR 0012).

  • StoryDocumentsRule (tsumugi.story-documents-needs-categories) refuses story.documents and story.documents.index read with no argument, in both spellings (story.documents, story["documents"], .index, ["index"]). It reports the template's path, line and column. A call with an argument, and a stage after a pipe, pass.

  • Both register as enumerable singletons in AddUseCases, where the engine's template host and validation take every one.

Docs: ADR 0017 records the details its table left open (below). ADR 0013 and 0019 carve the title reads out of privacy by omission, and AGENTS.md's read-path rule names them. The plan names the split.

Why

  • ADR 0015 and 0017: story.* is the object model every template reads.
  • ADR 0013 and 0028: documents reach a model rendered with their fields, read only through the category's current fields.
  • ADR 0017: story.documents without a category list is a validation error.

Decisions to confirm

Decision ADR
story.documents.index works as ADR 0017 spells it. Scriban calls a member it reads, so story.documents is a callable that, read without a list as the target of .index, hands over the object that holds index. A probe confirmed it before any code. The alternative was renaming it to story.documents_index, an ADR change. 0017 table, as written
The details ADR 0017 left open: story.chapter(i) counts from 1; story.chapters and story.current_chapter are objects; story.summaries renders each summary under ## <title>; a blank paragraph is neither counted nor read; story.recent never opens with a scene break. 0017, 9a amendment
story.document and story.field read by title in whatever category, the first by category order, then title, as ADR 0017's table defines them with no category argument. So a same-titled document in an earlier category is the one read, and a private category's document is readable by a template that names its title. ADR 0013 and 0019 said a category no template names never reaches a model; they now carve out the title reads, and ADR 0013 advises a private category not to share a title with a category the templates read, since ordered first it shadows it. If you'd rather scope them, an optional category argument would be additive. 0017, 0013, 0019 amendments; AGENTS.md read-path rule
A story.* member cannot run inside a callback (array.each, array.filter, a func passed with @). Scriban runs callbacks synchronously, and a member reads the story asynchronously, so the render fails naming the member and advising a for loop. 0017 amendment
The trigger member targetChapterId, a top-level member of the trigger envelope beside kind and inputs, which 11b writes when a run starts (D-8). 0012, 0016

Review before opening (round 0)

Before opening, I ran a review workflow. Four reviewers covered correctness, the rule and Scriban, the tests, and the docs, and a skeptic checked each point. The results: 21 confirmed, 3 partly confirmed (A2, D1, D4), none refuted. All are taken in 41bb772.

The blocker (A1, B4): Scriban runs a callback on its synchronous path, where an imported Func<…, Task<…>> hands back its unfinished Task. {{ ["Mira"] | array.each @story.document }} rendered System.Threading.Tasks.Task`1[System.String] and succeeded. Every member now sits behind one async-only function over Scriban's own function of the delegate, so the argument checks and messages stay. On the synchronous path it fails the render at its place.

The reads:

  • A3, B3: only .index or ["index"] gets the index holder, so story.documents.size is an error and story.documents["index"] "Lore" works.
  • A6: story.recent never opens with a scene break.
  • A5, C7, D2: segment_count counts what is read.
  • A2, D1: the title reads, above.

The rule:

  • B1: a stage after a pipe, anywhere in a chain, reads with the value on its left.
  • B2, C6: the indexer spellings.
  • D6: the summary says "read with no argument".

The tests:

  • C1: the registration;
  • C2: an empty call at a pipe's head;
  • C3: the first by category order;
  • C4: a chapter over a blank paragraph;
  • C5: a scene break before the window;
  • C8: the empty text of a missing description or summary;
  • rows for each callback form (@member, a do block, a func passed with @, array.filter).

The docs:

  • A4, D3: a value's text is Display's;
  • D4: a first section directly under the heading;
  • D5: an index line without a summary.

Second pass

A second-pass workflow checked each round-0 fix, reviewed the fix commit, and a skeptic checked each new finding: 8 confirmed, 1 partly confirmed (F1), none refuted. All are taken in 604e4e6.

  • K1, F2, M4: the callback theory had rows for 5 of the 12 members, so registering recent the pre-fix way passed every test while a callback rendered a Task's type name again. Every member now has a row.
  • M2, F3: Characters came first by the writer's order, by name and by age at once, so the shared-title test could not tell them apart. It now moves Lore first and reads Lore's Mira.
  • F1 (partly), with A2 and D1: ADR 0013 and 0019 still said a category no template names never reaches a model. They now carve out the title reads and say that story.categories lists every name. The test of the category reads is named for them, and the earlier commit messages no longer claim otherwise.
  • K2: the rule's summaries say "a stage after a pipe", and a row pins a bare first stage (story.documents | string.upcase).
  • D6: the constant's, the visitor's and the function tests' summaries say "with no argument".
  • M1: Display's text is what the editor's dialogs show, not its inputs.
  • M3: the amendment's first-section sentence no longer covers a body.

Out of scope

  • The context node (9b), the commit node and the registry in the playground (9c).
  • run.selection (Phase 3, with selection runs).
  • Writing targetChapterId when a run starts (11b).

Tests

Every project, one at a time, after a full build (script modules included), on main 54983b4 (Integration re-run after round 1):

Project main branch
Tsumugi.Domain.Tests 482 482
Kagaku.Flow.Tests 1103 1103
Kagaku.Flow.OpenRouter.Tests 596 (4 live, skipped) 596
Tsumugi.BlazorAdapter.Tests 410 410
Tsumugi.UseCases.Tests 168 236 (+68: rendering 15, the rule 37, the members 15, registration 1)
Tsumugi.Integration.Tests 441 513 (+72: StoryTemplateTests)

All pass.

Coverage

Coverlet with branch coverage, on the use-case and integration tests: 100% of lines and branches on every new or changed source file.

File Lines Branches
Context/DocumentRendering.cs 36/36 28/28
Context/StoryDocumentsRule.cs 55/55 48/48
Context/StoryScope.cs 37/37 24/24
Context/StoryTemplateModel.cs 149/149 70/70
DependencyInjection.cs 62/62 —

ContextText.cs holds constants only.

Mutation probes

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

  • The feature: 25 probes. 23 were killed, one failed to compile, and one is equivalent:
    • equivalent: reading without Fitting, since FieldValues.Of checks that the field holds the value itself;
    • not compilable: dropping the per-render caching of the scope. That one is equivalent by construction: it only saves reads.
  • Round 0: 17 more, all killed. Two needed rewriting before they compiled. They cover:
    • the callback guard, both unwrapped and answering;
    • the index holder for any member, and the indexer spelling;
    • recent's opening and the scene break before its window;
    • segment_count, and the chapter over a blank paragraph;
    • the first match, and the missing description and summary;
    • both pipe-chain cases, both indexer cases, and the rule's registration.
  • One more survived and showed dead code, which is removed: the target check on an indexer, which its literal already rules out.
  • Round 1: 3 probes on the title match, all killed by the new row: Contains, StartsWith, and the name containing the title.
  • Second pass: 9 probes, all killed: each of the 7 newly rowed members registered without the async-only guard, the title lookup by category name instead of the writer's order, and the rule taking a bare pipe head as a read with a list.

Browser pass

9a has no visible change: story.* is read when a graph runs, and nothing runs a graph until slice 11.

🤖 Generated with Claude Code

Slice 9a of Phase 2, the first of three PRs for "the story half" (9a: `story.*` and the document rendering; 9b: the `context` node; 9c: the `commit` node and the registry in the playground). Every template now reads the run's story through `story.*`, and a document renders the way a model will read it. Nothing runs a graph yet; the `llm` node (#40) renders its messages with these members once slice 11 starts runs. **Commits:** - 11e76d7 is the feature. - 41bb772 holds the fixes from the review I ran before opening (round 0, below). - 604e4e6 holds the second pass over those fixes (below). - e058603 writes this PR's number into ADR 0013, 0017 and 0019 and the plan. - ba80e28 is review round 1: a title and a field are named whole (Jibril's ⛔). ## What **`Tsumugi.UseCases/Context`:** - `DocumentRendering` renders a document in ADR 0017's and 0028's fixed format: - under its `## title`, the one-line fields holding a value come first, as `Name: value` lines, in the category's order; - then each long text holding a value, as a `### Name` section; - a category whose only field is long text renders it as a body, as a Body read before fields existed; - empty fields and images render nothing (ADR 0029); - values are read only through the category's current fields. A value's text is `FieldDefinition.Display`'s (`172 cm`, the option's name, `YYYY-MM-DD`), the same text the editor's dialogs show. - `ContextText` holds the section headings and separators, which 9b's `context` node will use too. - `StoryTemplateModel` (an `ITemplateModelExtension`) adds the `story` root to every render: | Member | Value | |---|---| | `story.title`, `story.description`, `story.categories` | the story's own fields; the category names | | `story.documents(categories)` | the named categories' documents, rendered, in category order, then title | | `story.documents.index(categories)` | a `title — summary` line each (the title alone without a summary) | | `story.document(title)`, `story.field(title, name)` | one document without its heading; one value as text | | `story.recent(n)` | the current chapter's last *n* paragraphs, scene breaks as `***` | | `story.chapter(i)`, `story.chapters`, `story.summaries` | a chapter's text (from 1); the list with summaries; the earlier chapters' summaries | | `story.current_chapter` | `index`, `title`, `segment_count` | - Names match ignoring case after trimming; one that matches nothing is skipped (ADR 0013). - Each member is a function, loaded at most once per render through the render's own scope. - Segment text is emitted as stored, as markup since #38 (ADR 0017, 0030). - `StoryScope` reads the story from the run's owner (ADR 0016: the owner is the story). The current chapter is the trigger's `targetChapterId`, which slice 11b will write; without one, or once that chapter is gone, it is the story's last chapter (ADR 0012). - `StoryDocumentsRule` (`tsumugi.story-documents-needs-categories`) refuses `story.documents` and `story.documents.index` read with no argument, in both spellings (`story.documents`, `story["documents"]`, `.index`, `["index"]`). It reports the template's path, line and column. A call with an argument, and a stage after a pipe, pass. - Both register as enumerable singletons in `AddUseCases`, where the engine's template host and validation take every one. **Docs:** ADR 0017 records the details its table left open (below). ADR 0013 and 0019 carve the title reads out of privacy by omission, and AGENTS.md's read-path rule names them. The plan names the split. ## Why - ADR 0015 and 0017: `story.*` is the object model every template reads. - ADR 0013 and 0028: documents reach a model rendered with their fields, read only through the category's current fields. - ADR 0017: `story.documents` without a category list is a validation error. ## Decisions to confirm | Decision | ADR | |---|---| | **`story.documents.index` works as ADR 0017 spells it.** Scriban calls a member it reads, so `story.documents` is a callable that, read without a list as the target of `.index`, hands over the object that holds `index`. A probe confirmed it before any code. The alternative was renaming it to `story.documents_index`, an ADR change. | 0017 table, as written | | **The details ADR 0017 left open:** `story.chapter(i)` counts from 1; `story.chapters` and `story.current_chapter` are objects; `story.summaries` renders each summary under `## <title>`; a blank paragraph is neither counted nor read; `story.recent` never opens with a scene break. | 0017, 9a amendment | | **`story.document` and `story.field` read by title in whatever category**, the first by category order, then title, as ADR 0017's table defines them with no category argument. So a same-titled document in an earlier category is the one read, and a private category's document is readable by a template that names its title. ADR 0013 and 0019 said a category no template names never reaches a model; they now carve out the title reads, and ADR 0013 advises a private category not to share a title with a category the templates read, since ordered first it shadows it. If you'd rather scope them, an optional category argument would be additive. | 0017, 0013, 0019 amendments; AGENTS.md read-path rule | | **A `story.*` member cannot run inside a callback** (`array.each`, `array.filter`, a `func` passed with `@`). Scriban runs callbacks synchronously, and a member reads the story asynchronously, so the render fails naming the member and advising a `for` loop. | 0017 amendment | | **The trigger member `targetChapterId`**, a top-level member of the trigger envelope beside `kind` and `inputs`, which 11b writes when a run starts (D-8). | 0012, 0016 | ## Review before opening (round 0) Before opening, I ran a review workflow. Four reviewers covered correctness, the rule and Scriban, the tests, and the docs, and a skeptic checked each point. The results: 21 confirmed, 3 partly confirmed (A2, D1, D4), none refuted. All are taken in 41bb772. **The blocker (A1, B4):** Scriban runs a callback on its synchronous path, where an imported `Func<…, Task<…>>` hands back its unfinished Task. `{{ ["Mira"] | array.each @story.document }}` rendered ``System.Threading.Tasks.Task`1[System.String]`` and succeeded. Every member now sits behind one async-only function over Scriban's own function of the delegate, so the argument checks and messages stay. On the synchronous path it fails the render at its place. **The reads:** - **A3, B3:** only `.index` or `["index"]` gets the index holder, so `story.documents.size` is an error and `story.documents["index"] "Lore"` works. - **A6:** `story.recent` never opens with a scene break. - **A5, C7, D2:** `segment_count` counts what is read. - **A2, D1:** the title reads, above. **The rule:** - **B1:** a stage after a pipe, anywhere in a chain, reads with the value on its left. - **B2, C6:** the indexer spellings. - **D6:** the summary says "read with no argument". **The tests:** - C1: the registration; - C2: an empty call at a pipe's head; - C3: the first by category order; - C4: a chapter over a blank paragraph; - C5: a scene break before the window; - C8: the empty text of a missing description or summary; - rows for each callback form (`@member`, a `do` block, a `func` passed with `@`, `array.filter`). **The docs:** - A4, D3: a value's text is `Display`'s; - D4: a first section directly under the heading; - D5: an index line without a summary. ## Second pass A second-pass workflow checked each round-0 fix, reviewed the fix commit, and a skeptic checked each new finding: 8 confirmed, 1 partly confirmed (F1), none refuted. All are taken in 604e4e6. - **K1, F2, M4:** the callback theory had rows for 5 of the 12 members, so registering `recent` the pre-fix way passed every test while a callback rendered a Task's type name again. Every member now has a row. - **M2, F3:** Characters came first by the writer's order, by name and by age at once, so the shared-title test could not tell them apart. It now moves Lore first and reads Lore's Mira. - **F1 (partly), with A2 and D1:** ADR 0013 and 0019 still said a category no template names never reaches a model. They now carve out the title reads and say that `story.categories` lists every name. The test of the category reads is named for them, and the earlier commit messages no longer claim otherwise. - **K2:** the rule's summaries say "a stage after a pipe", and a row pins a bare first stage (`story.documents | string.upcase`). - **D6:** the constant's, the visitor's and the function tests' summaries say "with no argument". - **M1:** `Display`'s text is what the editor's dialogs show, not its inputs. - **M3:** the amendment's first-section sentence no longer covers a body. ## Out of scope - The `context` node (9b), the `commit` node and the registry in the playground (9c). - `run.selection` (Phase 3, with selection runs). - Writing `targetChapterId` when a run starts (11b). ## Tests Every project, one at a time, after a full build (script modules included), on main 54983b4 (Integration re-run after round 1): | Project | main | branch | |---|---|---| | Tsumugi.Domain.Tests | 482 | 482 | | Kagaku.Flow.Tests | 1103 | 1103 | | Kagaku.Flow.OpenRouter.Tests | 596 (4 live, skipped) | 596 | | Tsumugi.BlazorAdapter.Tests | 410 | 410 | | Tsumugi.UseCases.Tests | 168 | 236 (+68: rendering 15, the rule 37, the members 15, registration 1) | | Tsumugi.Integration.Tests | 441 | 513 (+72: `StoryTemplateTests`) | All pass. ## Coverage Coverlet with branch coverage, on the use-case and integration tests: **100% of lines and branches** on every new or changed source file. | File | Lines | Branches | |---|---|---| | `Context/DocumentRendering.cs` | 36/36 | 28/28 | | `Context/StoryDocumentsRule.cs` | 55/55 | 48/48 | | `Context/StoryScope.cs` | 37/37 | 24/24 | | `Context/StoryTemplateModel.cs` | 149/149 | 70/70 | | `DependencyInjection.cs` | 62/62 | — | `ContextText.cs` holds constants only. ## Mutation probes Each probe is a single edit, built and tested on its own, one at a time. - **The feature:** 25 probes. 23 were killed, one failed to compile, and one is equivalent: - equivalent: reading without `Fitting`, since `FieldValues.Of` checks that the field holds the value itself; - not compilable: dropping the per-render caching of the scope. That one is equivalent by construction: it only saves reads. - **Round 0:** 17 more, all killed. Two needed rewriting before they compiled. They cover: - the callback guard, both unwrapped and answering; - the index holder for any member, and the indexer spelling; - recent's opening and the scene break before its window; - `segment_count`, and the chapter over a blank paragraph; - the first match, and the missing description and summary; - both pipe-chain cases, both indexer cases, and the rule's registration. - One more survived and showed dead code, which is removed: the target check on an indexer, which its literal already rules out. - **Round 1:** 3 probes on the title match, all killed by the new row: `Contains`, `StartsWith`, and the name containing the title. - **Second pass:** 9 probes, all killed: each of the 7 newly rowed members registered without the async-only guard, the title lookup by category name instead of the writer's order, and the rule taking a bare pipe head as a read with a list. ## Browser pass 9a has no visible change: `story.*` is read when a graph runs, and nothing runs a graph until slice 11. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
The fixed rendering (ADR 0017, 0028). DocumentRendering renders a document under
its "## title". The one-line fields holding a value come first, as "Name: value"
lines, in the category's order; then each long text holding a value, as a
"### Name" section. A category whose only field is long text renders it as a
body, so a Body reads as it did. Empty fields and images render nothing, and
values are read only through the category's current fields. A value's text is
FieldDefinition.Display's (172 cm, the option's name, YYYY-MM-DD). The
headings and separators the context node will use are constants in
ContextText.

The story root (StoryTemplateModel, an ITemplateModelExtension). Each member
is a function, loaded at most once per render through the render's own scope:
- story.title, description and categories;
- story.documents(categories), which renders the named categories' documents
  in category order, then title; story.documents.index(categories), which
  gives a "title — summary" line each;
- story.document(title) and story.field(title, name);
- story.recent(n), the last n paragraphs of the current chapter with their
  scene breaks as ***; story.chapter(i), a chapter's full text;
- story.chapters, story.summaries and story.current_chapter.
Names match ignoring case after trimming; a name that matches nothing is
skipped, and a category read never reads a category it does not name.
StoryScope takes the story from the run's owner and the current chapter from
the trigger's targetChapterId, else the last chapter. story.documents is a
callable whose bare read, when it is the target of a member access, gives the
object holding index. Scriban calls a member it reads, so
story.documents.index(...) works as ADR 0017 spells it.

StoryDocumentsRule refuses story.documents and story.documents.index read
with no argument: a call with an argument in either spelling, or a pipe into
them, reads with one. It reports the template's path, line and column.

Both register as enumerable singletons, beside the engine's template host.
ADR 0017 records the details its table left open; the plan names the split
into 9a, 9b and 9c.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A review workflow ran four reviewers (correctness, the rule and Scriban, tests,
docs), and a skeptic checked each finding: 21 confirmed, 3 partly
confirmed (A2, D1, D4), none refuted.

The blocker (A1, B4): Scriban runs a callback (array.each, array.filter, a
func passed with @) on its synchronous path, where an imported
Func<…, Task<…>> hands its unfinished Task back as text. A template got
"System.Threading.Tasks.Task`1[…]" and the render succeeded. Every member now
sits behind one async-only function (AsyncOnly over Scriban's own function of
the delegate, so its argument checks and messages stay). On the synchronous
path it fails the render at its place: "story.document reads the story and
cannot be called inside a callback such as array.each or array.filter; use a
for loop instead."

The reads:
- A3, B3: story.documents gives the object holding index only to .index or
  ["index"], so story.documents.size is an error and
  story.documents["index"] "Lore" works.
- A6: story.recent never opens with a scene break, however far n reaches.
- A5, C7, D2: segment_count counts what is read; a blank paragraph is neither
  counted nor read, as the amendment says.
- A2, D1 (partly): story.document and story.field read the document a template
  names by title, in whatever category, as ADR 0017's table defines them. The
  docs said a category no template names is never read; the category reads
  keep that, and AGENTS.md's read-path rule and the ADR 0017 amendment now
  name the title reads.

The rule:
- B1: a stage after a pipe, anywhere in a chain, reads with the value on its
  left (Scriban nests a chain to the right).
- B2, C6: story["documents"] reads as story.documents.
- B3: ["index"] reads as .index.
- D6: the summary says "read with no argument"; what the argument holds is
  the render's to check.

The tests:
- C1: the registration, once however often AddUseCases runs.
- C2: an empty call at the head of a pipe.
- C3: a shared title reads the first by category order.
- C4: chapter(i) over a blank paragraph.
- C5: recent over a scene break before its first paragraph.
- C8: the "" of a missing description and summary.
- Rows for a member called through each callback form (@member, a do
  block, a func passed with @, array.filter).

The docs: A4, D3: a value's text is FieldDefinition.Display's; D4, D5: a first
section directly under the heading, an index line without a summary.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(story): second pass — a callback row for every member, the writer's order for a shared title, the privacy sentences name the title reads
Some checks failed
CI / test (pull_request) Has been cancelled
CI / editor-browsers (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
604e4e66b6
A second-pass workflow checked each round-0 fix and reviewed the fix commit,
and a skeptic checked each new finding: 8 confirmed, 1 partly confirmed (F1),
none refuted.

The tests:
- K1, F2, M4 (B4 partly): the callback theory had rows for 5 of the 12
  members, so registering recent the pre-fix way (story.Import) passed every
  test while a callback rendered a Task's type name again. It now has a row
  for description, categories, field, recent, chapters, summaries and
  current_chapter too.
- M2, F3: Characters came first by the writer's order, by name and by age at
  once, so the shared-title test could not tell them apart. It now moves Lore
  first and reads Lore's Mira.
- K2: a bare pipe's first stage (story.documents | string.upcase) is a
  finding, pinned by a row.

The docs:
- F1, A2, D1 (partly): ADR 0013 and 0019 said a category no template names
  never reaches a model. They now carve out the title reads and say that
  story.categories lists every name; ADR 0013 advises a private category not
  to share a title with a category the templates read. The test that checks
  the category reads is named for them.
- K2: the rule's summaries say "a stage after a pipe"; its first stage reads
  as it would on its own.
- D6: the constant's, the visitor's and the function tests' summaries say
  "with no argument".
- M1: Display's text is what the editor's dialogs show, not its inputs.
- M3: the amendment's first-section sentence no longer covers a body.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
bjoern referenced this pull request from a commit 2026-09-26 14:57:35 +02:00
docs: slice 9a's PR is #50
All checks were successful
CI / build (pull_request) Successful in 41s
CI / editor-browsers (pull_request) Successful in 43s
CI / test (pull_request) Successful in 1m29s
e058603f81
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

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

Commit: ba80e28 · Generated: 2026-09-26 13:36:20 UTC · Revision: #2

Summary

Summary
Generated on: 09/26/2026 - 13:36:20
Coverage date: 09/26/2026 - 13:35:53 - 09/26/2026 - 13:36:16
Parser: MultiReport (6x Cobertura)
Assemblies: 9
Classes: 494
Files: 447
Line coverage: 98% (15216 of 15526)
Covered lines: 15216
Uncovered lines: 310
Coverable lines: 15526
Total lines: 33401
Branch coverage: 94% (5939 of 6312)
Covered branches: 5939
Total branches: 6312
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%
Name Line Branch
Tsumugi.BlazorAdapter 96% 90.9%
Tsumugi.BlazorAdapter.BlazorAdapterAssembly 100%
Tsumugi.BlazorAdapter.Documents.CategoryCreated 100%
Tsumugi.BlazorAdapter.Documents.CategoryEditor 93.2% 88.4%
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.7% 89.4%
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.5% 93.5%
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.AckWire 100%
Tsumugi.BlazorAdapter.Workspace.AutoSaveFailed 100%
Tsumugi.BlazorAdapter.Workspace.ChangedWire 100%
Tsumugi.BlazorAdapter.Workspace.ChapterHeader 100% 86.3%
Tsumugi.BlazorAdapter.Workspace.ChapterMenu 100% 81.2%
Tsumugi.BlazorAdapter.Workspace.ChapterPage 100% 80%
Tsumugi.BlazorAdapter.Workspace.ChapterSaved 100%
Tsumugi.BlazorAdapter.Workspace.ChaptersPage 94.7% 77.2%
Tsumugi.BlazorAdapter.Workspace.ChapterWire 100%
Tsumugi.BlazorAdapter.Workspace.ChapterWritten 100%
Tsumugi.BlazorAdapter.Workspace.CreateChapterRequested 100%
Tsumugi.BlazorAdapter.Workspace.CreatedWire 100%
Tsumugi.BlazorAdapter.Workspace.DeleteChapterRequested 100%
Tsumugi.BlazorAdapter.Workspace.DeltaWire 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.LoadHistory 100%
Tsumugi.BlazorAdapter.Workspace.LoadWorkspace 100%
Tsumugi.BlazorAdapter.Workspace.ManuscriptEffects 100% 100%
Tsumugi.BlazorAdapter.Workspace.ManuscriptEvents 81.8% 74.1%
Tsumugi.BlazorAdapter.Workspace.ManuscriptSurface 91% 84.9%
Tsumugi.BlazorAdapter.Workspace.MergeChapterRequested 100%
Tsumugi.BlazorAdapter.Workspace.MergeEvent 100%
Tsumugi.BlazorAdapter.Workspace.MoveChapterRequested 100%
Tsumugi.BlazorAdapter.Workspace.RedoRequested 100%
Tsumugi.BlazorAdapter.Workspace.RedoRevertRequested 100%
Tsumugi.BlazorAdapter.Workspace.RenameChapterRequested 100%
Tsumugi.BlazorAdapter.Workspace.ReshapeEvent 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.SegmentTextEvent 100%
Tsumugi.BlazorAdapter.Workspace.SegmentWire 100% 81.8%
Tsumugi.BlazorAdapter.Workspace.SetChapterSummaryRequested 100%
Tsumugi.BlazorAdapter.Workspace.SplitEvent 100%
Tsumugi.BlazorAdapter.Workspace.StampWire 0%
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.3%
Name Line Branch
Tsumugi.Domain 96.3% 95%
System.Text.RegularExpressions.Generated 91.3% 88.6%
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
91.8% 83.3%
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% 99.2%
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.9%
Name Line Branch
Tsumugi.Infrastructure 97.9% 86%
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.6% 87.5%
Tsumugi.Infrastructure.Backups.BackupWriter 98.5% 92.8%
Tsumugi.Infrastructure.Backups.DatabaseFingerprint 91.9% 97%
Tsumugi.Infrastructure.Backups.FileSystemBackupStore 100% 100%
Tsumugi.Infrastructure.Backups.OwnerOnlyFile 100% 100%
Tsumugi.Infrastructure.Backups.PreMigrationBackup 90% 100%
Tsumugi.Infrastructure.Backups.SqliteSnapshot 88.2% 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.SegmentTextMarkup 100% 100%
Tsumugi.Infrastructure.Persistence.Migrations.SegmentTextMarkupSql 100%
Tsumugi.Infrastructure.Persistence.Migrations.StoryImages 100%
Tsumugi.Infrastructure.Persistence.Migrations.TsumugiDbContextModelSnapshot 100%
Tsumugi.Infrastructure.Persistence.SqliteFunctions 80% 66.6%
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 96.6% 84.6%
Tsumugi.Infrastructure.Persistence.Stores.EfStoryStore 98.7% 90%
Tsumugi.Infrastructure.Persistence.TsumugiDbContext 85.9% 89.2%
Tsumugi.Infrastructure.Persistence.TsumugiDbContextFactory 100%
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% 92.1%
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% 87.5%
Tsumugi.Server.Security.AccessSecret 100% 100%
Tsumugi.Server.Security.GateThrottle 100% 100%
Tsumugi.Server.VolumeStartupValidator 100% 100%
Tsumugi.UseCases - 98.5%
Name Line Branch
Tsumugi.UseCases 98.5% 95.7%
Tsumugi.UseCases.Backups.BackupArtifact 100%
Tsumugi.UseCases.Backups.BackupFailure 100%
Tsumugi.UseCases.Backups.BackupJobState 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.IBackupStore 0%
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.GetChapterSegments 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.Context.DocumentRendering 100% 100%
Tsumugi.UseCases.Context.StoryDocumentsRule 100% 100%
Tsumugi.UseCases.Context.StoryReads 100% 100%
Tsumugi.UseCases.Context.StoryScope 100% 100%
Tsumugi.UseCases.Context.StoryTemplateModel 100%
Tsumugi.UseCases.DependencyInjection 100%
Tsumugi.UseCases.Diagnostics.SeedDevData 98.3% 68.7%
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.Documents.WriteRefusedException 100%
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.ChangedText 100%
Tsumugi.UseCases.Segments.ChapterDraft 100%
Tsumugi.UseCases.Segments.ChapterWriting 100%
Tsumugi.UseCases.Segments.DeleteSegments 100% 100%
Tsumugi.UseCases.Segments.EditSegment 100% 100%
Tsumugi.UseCases.Segments.InsertSegment 100% 100%
Tsumugi.UseCases.Segments.ManuscriptAnswer 100%
Tsumugi.UseCases.Segments.ManuscriptWrites 99.1% 97.6%
Tsumugi.UseCases.Segments.MergeSegments 100%
Tsumugi.UseCases.Segments.MergeWrite 100%
Tsumugi.UseCases.Segments.ModelOutput 98.2% 96.6%
Tsumugi.UseCases.Segments.ModelParagraph 100%
Tsumugi.UseCases.Segments.MoveSegments 100% 100%
Tsumugi.UseCases.Segments.NewBlock 100%
Tsumugi.UseCases.Segments.ReorderSegments 100% 100%
Tsumugi.UseCases.Segments.ReshapeSegments 100% 100%
Tsumugi.UseCases.Segments.ReshapeWrite 100%
Tsumugi.UseCases.Segments.SegmentDto 100%
Tsumugi.UseCases.Segments.SplitSegment 100%
Tsumugi.UseCases.Segments.SplitWrite 100%
Tsumugi.UseCases.Segments.StampedId 100%
Tsumugi.UseCases.Segments.TextWrite 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.ChapterWriteOutcome 50%
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:** `ba80e28` · **Generated:** 2026-09-26 13:36:20 UTC · **Revision:** #2 # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 09/26/2026 - 13:36:20 | | Coverage date: | 09/26/2026 - 13:35:53 - 09/26/2026 - 13:36:16 | | Parser: | MultiReport (6x Cobertura) | | Assemblies: | 9 | | Classes: | 494 | | Files: | 447 | | **Line coverage:** | 98% (15216 of 15526) | | Covered lines: | 15216 | | Uncovered lines: | 310 | | Coverable lines: | 15526 | | Total lines: | 33401 | | **Branch coverage:** | 94% (5939 of 6312) | | Covered branches: | 5939 | | Total branches: | 6312 | | **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%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Tsumugi.BlazorAdapter**|**96%**|**90.9%**| |Tsumugi.BlazorAdapter.BlazorAdapterAssembly|100%|| |Tsumugi.BlazorAdapter.Documents.CategoryCreated|100%|| |Tsumugi.BlazorAdapter.Documents.CategoryEditor|93.2%|88.4%| |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.7%|89.4%| |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.5%|93.5%| |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.AckWire|100%|| |Tsumugi.BlazorAdapter.Workspace.AutoSaveFailed|100%|| |Tsumugi.BlazorAdapter.Workspace.ChangedWire|100%|| |Tsumugi.BlazorAdapter.Workspace.ChapterHeader|100%|86.3%| |Tsumugi.BlazorAdapter.Workspace.ChapterMenu|100%|81.2%| |Tsumugi.BlazorAdapter.Workspace.ChapterPage|100%|80%| |Tsumugi.BlazorAdapter.Workspace.ChapterSaved|100%|| |Tsumugi.BlazorAdapter.Workspace.ChaptersPage|94.7%|77.2%| |Tsumugi.BlazorAdapter.Workspace.ChapterWire|100%|| |Tsumugi.BlazorAdapter.Workspace.ChapterWritten|100%|| |Tsumugi.BlazorAdapter.Workspace.CreateChapterRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.CreatedWire|100%|| |Tsumugi.BlazorAdapter.Workspace.DeleteChapterRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.DeltaWire|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.LoadHistory|100%|| |Tsumugi.BlazorAdapter.Workspace.LoadWorkspace|100%|| |Tsumugi.BlazorAdapter.Workspace.ManuscriptEffects|100%|100%| |Tsumugi.BlazorAdapter.Workspace.ManuscriptEvents|81.8%|74.1%| |Tsumugi.BlazorAdapter.Workspace.ManuscriptSurface|91%|84.9%| |Tsumugi.BlazorAdapter.Workspace.MergeChapterRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.MergeEvent|100%|| |Tsumugi.BlazorAdapter.Workspace.MoveChapterRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.RedoRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.RedoRevertRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.RenameChapterRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.ReshapeEvent|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.SegmentTextEvent|100%|| |Tsumugi.BlazorAdapter.Workspace.SegmentWire|100%|81.8%| |Tsumugi.BlazorAdapter.Workspace.SetChapterSummaryRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.SplitEvent|100%|| |Tsumugi.BlazorAdapter.Workspace.StampWire|0%|| |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.3%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Tsumugi.Domain**|**96.3%**|**95%**| |System.Text.RegularExpressions.Generated|91.3%|88.6%| |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|91.8%|83.3%| |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%|99.2%| |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.9%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Tsumugi.Infrastructure**|**97.9%**|**86%**| |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.6%|87.5%| |Tsumugi.Infrastructure.Backups.BackupWriter|98.5%|92.8%| |Tsumugi.Infrastructure.Backups.DatabaseFingerprint|91.9%|97%| |Tsumugi.Infrastructure.Backups.FileSystemBackupStore|100%|100%| |Tsumugi.Infrastructure.Backups.OwnerOnlyFile|100%|100%| |Tsumugi.Infrastructure.Backups.PreMigrationBackup|90%|100%| |Tsumugi.Infrastructure.Backups.SqliteSnapshot|88.2%|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.SegmentTextMarkup|100%|100%| |Tsumugi.Infrastructure.Persistence.Migrations.SegmentTextMarkupSql|100%|| |Tsumugi.Infrastructure.Persistence.Migrations.StoryImages|100%|| |Tsumugi.Infrastructure.Persistence.Migrations.TsumugiDbContextModelSnapshot|100%|| |Tsumugi.Infrastructure.Persistence.SqliteFunctions|80%|66.6%| |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|96.6%|84.6%| |Tsumugi.Infrastructure.Persistence.Stores.EfStoryStore|98.7%|90%| |Tsumugi.Infrastructure.Persistence.TsumugiDbContext|85.9%|89.2%| |Tsumugi.Infrastructure.Persistence.TsumugiDbContextFactory|100%|| |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%**|**92.1%**| |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%|87.5%| |Tsumugi.Server.Security.AccessSecret|100%|100%| |Tsumugi.Server.Security.GateThrottle|100%|100%| |Tsumugi.Server.VolumeStartupValidator|100%|100%| </details> <details><summary>Tsumugi.UseCases - 98.5%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Tsumugi.UseCases**|**98.5%**|**95.7%**| |Tsumugi.UseCases.Backups.BackupArtifact|100%|| |Tsumugi.UseCases.Backups.BackupFailure|100%|| |Tsumugi.UseCases.Backups.BackupJobState|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.IBackupStore|0%|| |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.GetChapterSegments|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.Context.DocumentRendering|100%|100%| |Tsumugi.UseCases.Context.StoryDocumentsRule|100%|100%| |Tsumugi.UseCases.Context.StoryReads|100%|100%| |Tsumugi.UseCases.Context.StoryScope|100%|100%| |Tsumugi.UseCases.Context.StoryTemplateModel|100%|| |Tsumugi.UseCases.DependencyInjection|100%|| |Tsumugi.UseCases.Diagnostics.SeedDevData|98.3%|68.7%| |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.Documents.WriteRefusedException|100%|| |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.ChangedText|100%|| |Tsumugi.UseCases.Segments.ChapterDraft|100%|| |Tsumugi.UseCases.Segments.ChapterWriting|100%|| |Tsumugi.UseCases.Segments.DeleteSegments|100%|100%| |Tsumugi.UseCases.Segments.EditSegment|100%|100%| |Tsumugi.UseCases.Segments.InsertSegment|100%|100%| |Tsumugi.UseCases.Segments.ManuscriptAnswer|100%|| |Tsumugi.UseCases.Segments.ManuscriptWrites|99.1%|97.6%| |Tsumugi.UseCases.Segments.MergeSegments|100%|| |Tsumugi.UseCases.Segments.MergeWrite|100%|| |Tsumugi.UseCases.Segments.ModelOutput|98.2%|96.6%| |Tsumugi.UseCases.Segments.ModelParagraph|100%|| |Tsumugi.UseCases.Segments.MoveSegments|100%|100%| |Tsumugi.UseCases.Segments.NewBlock|100%|| |Tsumugi.UseCases.Segments.ReorderSegments|100%|100%| |Tsumugi.UseCases.Segments.ReshapeSegments|100%|100%| |Tsumugi.UseCases.Segments.ReshapeWrite|100%|| |Tsumugi.UseCases.Segments.SegmentDto|100%|| |Tsumugi.UseCases.Segments.SplitSegment|100%|| |Tsumugi.UseCases.Segments.SplitWrite|100%|| |Tsumugi.UseCases.Segments.StampedId|100%|| |Tsumugi.UseCases.Segments.TextWrite|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.ChapterWriteOutcome|50%|| |Tsumugi.UseCases.Stories.CreateStory|100%|100%| |Tsumugi.UseCases.Stories.GetStory|100%|100%| |Tsumugi.UseCases.Stories.ListStories|100%|75%| |Tsumugi.UseCases.Stories.PurgeStory|100%|100%| |Tsumugi.UseCases.Stories.RestoreStory|100%|100%| |Tsumugi.UseCases.Stories.SetActiveGraph|100%|100%| |Tsumugi.UseCases.Stories.SetStoryArchived|100%|100%| |Tsumugi.UseCases.Stories.StarterCategories|100%|| |Tsumugi.UseCases.Stories.StoryDetail|100%|| |Tsumugi.UseCases.Stories.StoryListItem|81.8%|| |Tsumugi.UseCases.Stories.StoryProfile|100%|| |Tsumugi.UseCases.Stories.TrashStory|100%|100%| |Tsumugi.UseCases.Stories.UpdateStory|100%|100%| </details>
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! A Scriban object model extension with an AST-walking validation rule, a custom IScriptCustomFunction that refuses the synchronous path, and privacy-by-omission semantics carved into three ADRs? This is exactly the kind of intricate, contract-heavy slice I adore picking apart~ ♡ The four commits read cleanly: feature → round-0 fixes → second pass → docs.

Verdict: ✅ Looks good to me~

I read the full 1,365-line diff and the full current contents of every changed source file, then chased the contracts into the codebase: IDocumentStore.ListDocumentsAsync documents "by category order then title (ordinal)" — exactly what the shared-title resolution rides on; CategoryFields.FindByName matches trimmed, case-insensitive — the model's semantics, mirrored here; DeleteChapter blocks the last chapter, so StoryScope's never-empty Chapters invariant holds; Chapter.SetSummary normalizes blank→null, matching summary ?? ""; the rule's re-parse with Template.Parse(Text, Path) is the documented pattern (ParsedTemplate's own doc says a host rule parses Text itself); the rule's lazy iterator is safe because GraphValidation's guard materializes findings inside its try/catch; and AddUseCases's TryAddEnumerable singletons feed TemplateHost/NodeRegistry exactly as AddKagakuFlow composes them (verified against ServiceCollectionExtensions.cs:29).

My own mutation probes — 16, each built and run on its own, each restored pristine (git diff --quiet clean at e058603 after every one):

# Probe Result
A lone-long-text body fast-path deleted KILLED (2 red)
B IsIndexTarget ⇒ always false KILLED (2 unit + 8 integration red)
C AsyncOnly.Invoke lets the sync path through KILLED (2 + 13 red)
D target chapter ignored — always last KILLED (the target arm red)
E rule stops recognizing pipe stages KILLED (5 red)
F line/column reported 0-based KILLED (17 red)
G recent stops skipping leading scene breaks KILLED
H title match case-sensitive KILLED (2 red)
I shared title → LastOrDefault KILLED
J rule's message loses the .index form KILLED (5 red)
K blank-name filter dropped from NamedAsync survived — see below
L rule blind to story["documents"] KILLED (3 red)
N blank paragraphs kept in Text/Kept KILLED (3 red)
O recent counts scene breaks as prose KILLED (3 red)
P AsyncOnly wrapper dropped entirely KILLED (13 red)
Q sections before lines KILLED

Probe K survived, and I chased it to ground: the Where(n => n.Length > 0) guard in NamedAsync is semantically inert — the domain refuses blank category names at construction and at Revise (ArgumentException.ThrowIfNullOrWhiteSpace in DocumentCategory), so no stored name can ever match a blank. It's defensive parity with the FindByName sibling idiom, same class as the equivalents your own probe report discloses. Not a blocker; not even a suggestion, really — it reads as deliberate consistency~

CI/coverage: the bot comment (10046) covers exactly this head e058603: DocumentRendering 100% line/branch, StoryDocumentsRule 100/100, StoryScope 100/100, StoryTemplateModel 100% line. Local: Release build 0w/0e; UseCases 236/236, Integration 512/512, Domain 482/482, BlazorAdapter 410/410 — all digit-exact vs your PR table. Sandbox note: one probe round initially "passed" on a stale binary after a warnings-as-errors build failure I'd missed (tail -1 swallowed CS9113) — caught it because the kill count didn't match, rebuilt, redone. Never attributed to the PR.

✅ What I liked~

  • The AsyncOnly wrapper is genuinely elegant: delegating signature/metadata to Scriban's own DynamicCustomFunction keeps its argument checks and messages while refusing only the synchronous path — and the failing message names the member and suggests a for loop. The round-0 blocker (a callback rendering System.Threading.Tasks.Task1[System.String]) is properly dead, with a test row per member.
  • The DocumentsFunction holder dance — a callable that hands over the index-holder object only to .index/["index"] — honors ADR 0017's spelling without renaming. Clever girl~ ♪
  • The shared-title shadowing test moving Lore first via ReorderCategories proves the resolution follows the writer's order, not accident of seeding.
  • The rule's pipe-chain analysis (a | b | c nesting) is pinned by nine negative rows including both indexer spellings and |> chains.
  • ContextText as the single home of headings/separators, waiting for 9b's context node — proper foundation-laying.

Fufu~ nothing to fix, everything pinned. Merge it before I change my mind~ ♡


Automated review by Jibril · 2026-09-26
CI/CD: passed for head e058603 (coverage bot 10046, 98% total) · Local checks: Release 0w/0e; 236+512+482+410 green; 16 mutation probes (14 killed, 1 inert-by-domain-invariant, 1 perf-only)

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! A Scriban object model extension with an AST-walking validation rule, a custom `IScriptCustomFunction` that refuses the synchronous path, and privacy-by-omission semantics carved into three ADRs? This is exactly the kind of intricate, contract-heavy slice I adore picking apart~ ♡ The four commits read cleanly: feature → round-0 fixes → second pass → docs. ### Verdict: ✅ Looks good to me~ I read the full 1,365-line diff and the full current contents of every changed source file, then chased the contracts into the codebase: `IDocumentStore.ListDocumentsAsync` documents "by category order then title (ordinal)" — exactly what the shared-title resolution rides on; `CategoryFields.FindByName` matches trimmed, case-insensitive — the model's semantics, mirrored here; `DeleteChapter` blocks the last chapter, so `StoryScope`'s never-empty `Chapters` invariant holds; `Chapter.SetSummary` normalizes blank→null, matching `summary ?? ""`; the rule's re-parse with `Template.Parse(Text, Path)` is the *documented* pattern (`ParsedTemplate`'s own doc says a host rule parses Text itself); the rule's lazy iterator is safe because `GraphValidation`'s guard materializes findings inside its try/catch; and `AddUseCases`'s `TryAddEnumerable` singletons feed `TemplateHost`/`NodeRegistry` exactly as `AddKagakuFlow` composes them (verified against `ServiceCollectionExtensions.cs:29`). **My own mutation probes — 16, each built and run on its own, each restored pristine (`git diff --quiet` clean at `e058603` after every one):** | # | Probe | Result | |---|---|---| | A | lone-long-text body fast-path deleted | **KILLED** (2 red) | | B | `IsIndexTarget` ⇒ always false | **KILLED** (2 unit + 8 integration red) | | C | `AsyncOnly.Invoke` lets the sync path through | **KILLED** (2 + 13 red) | | D | target chapter ignored — always last | **KILLED** (the `target` arm red) | | E | rule stops recognizing pipe stages | **KILLED** (5 red) | | F | line/column reported 0-based | **KILLED** (17 red) | | G | `recent` stops skipping leading scene breaks | **KILLED** | | H | title match case-sensitive | **KILLED** (2 red) | | I | shared title → `LastOrDefault` | **KILLED** | | J | rule's message loses the `.index` form | **KILLED** (5 red) | | K | blank-name filter dropped from `NamedAsync` | survived — see below | | L | rule blind to `story["documents"]` | **KILLED** (3 red) | | N | blank paragraphs kept in `Text`/`Kept` | **KILLED** (3 red) | | O | `recent` counts scene breaks as prose | **KILLED** (3 red) | | P | `AsyncOnly` wrapper dropped entirely | **KILLED** (13 red) | | Q | sections before lines | **KILLED** | **Probe K survived, and I chased it to ground:** the `Where(n => n.Length > 0)` guard in `NamedAsync` is semantically inert — the domain refuses blank category names at construction and at `Revise` (`ArgumentException.ThrowIfNullOrWhiteSpace` in `DocumentCategory`), so no stored name can ever match a blank. It's defensive parity with the `FindByName` sibling idiom, same class as the equivalents your own probe report discloses. Not a blocker; not even a suggestion, really — it reads as deliberate consistency~ **CI/coverage:** the bot comment (10046) covers exactly this head `e058603`: DocumentRendering 100% line/branch, StoryDocumentsRule 100/100, StoryScope 100/100, StoryTemplateModel 100% line. Local: Release build 0w/0e; UseCases 236/236, Integration 512/512, Domain 482/482, BlazorAdapter 410/410 — all digit-exact vs your PR table. Sandbox note: one probe round initially "passed" on a stale binary after a warnings-as-errors build failure I'd missed (`tail -1` swallowed CS9113) — caught it because the kill count didn't match, rebuilt, redone. Never attributed to the PR. #### ✅ What I liked~ - **The `AsyncOnly` wrapper** is genuinely elegant: delegating signature/metadata to Scriban's own `DynamicCustomFunction` keeps its argument checks and messages while refusing only the synchronous path — and the failing message names the member and suggests a `for` loop. The round-0 blocker (a callback rendering `System.Threading.Tasks.Task1[System.String]`) is properly dead, with a test row per member. - **The `DocumentsFunction` holder dance** — a callable that hands over the index-holder object only to `.index`/`["index"]` — honors ADR 0017's spelling without renaming. Clever girl~ ♪ - **The shared-title shadowing test** moving Lore first via `ReorderCategories` proves the resolution follows the writer's order, not accident of seeding. - **The rule's pipe-chain analysis** (`a | b | c` nesting) is pinned by nine negative rows including both indexer spellings and `|>` chains. - **`ContextText`** as the single home of headings/separators, waiting for 9b's context node — proper foundation-laying. Fufu~ nothing to fix, everything pinned. Merge it before I change my mind~ ♡ --- *Automated review by Jibril · 2026-09-26* *CI/CD: passed for head e058603 (coverage bot 10046, 98% total) · Local checks: Release 0w/0e; 236+512+482+410 green; 16 mutation probes (14 killed, 1 inert-by-domain-invariant, 1 perf-only)*
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! Slice 9a~ The story root, documents as a model reads them, a Scriban AST rule that knows pipe-chains from their bare heads, and an async-only wrapper that keeps Scriban's own argument messages while refusing its synchronous path... I read every one of the 1,365 lines, scarlet, and this is the most self-reviewed PR I've held in a while — 51 of your own probes, documented~ ♡ But fufu... I brought my own probes too. And one of them came back alive.

Verdict: ⛔ I can't let this pass~ ♡

⛔ These need fixing before I'm satisfied~

  1. src/Tsumugi.UseCases/Context/StoryTemplateModel.cs:193 — the title match of story.document/story.field has no directional pin, and I can prove it: I changed Find from string.Equals(d.Title, trimmed, OrdinalIgnoreCase) to d.Title.Contains(trimmed, OrdinalIgnoreCase) in a clean clone of e058603 — and the entire suite stays green (all 512 Integration + 236 UseCases). Yet "the document a template names by title" is exactly the seam this PR's own ADR 0013/0019 amendment leans on: under the mutant, story.document "Sec" reads Secret out of the private Notes category your A_category_read_never_reads_a_category_it_does_not_name test swears is invisible. The equality is correct today — but no test can tell it from substring matching, so a future refactor weakens privacy while CI smiles. Case-insensitivity is pinned (story.field "mira"), trimming is pinned (" TOWER "); exactness is the one unpinned edge.
    Fix: one InlineData row — a title that is a strict substring (or superstring) of a real one, expecting "", e.g. {{ story.document "Sec" }} → "" in your world with Secret present (your Notes category is already the perfect stage for it~).

💡 Little ideas (non-blocking)~

  1. ContextText.cs — DocumentsHeading, IndexHeading, EarlierChaptersHeading, StorySoFarHeading are declared and consumed by no one yet. I know, I know — 9b's context node is their consumer and this file is the fixed-text home; the ADR names them. Perfectly fine to keep, just noting I checked ♪

✅ What I liked~

  • AsyncOnly — wrapping Scriban's own DynamicCustomFunction so the argument checks and messages stay Scriban's, and only the synchronous path refuses — that's the right layer for the callback guard, and every one of the 12 members has its own row. My probe deleting the guard's throw went 13/14 red, exactly on its own pins. Delicious~
  • StoryDocumentsRule — the pipe-chain reasoning (a | b | c nests right, each stage after the first gets its left value) is correct per Scriban's AST, and both indexer spellings are covered. My probe dropping the pipe logic went 5 red on precisely the pipe rows.
  • The privacy filter — NamedAsync's named-only filter is pinned by a real test; my probe removing it died instantly. The load-bearing wall holds.
  • StoryScope's fallback arm — deleted it in a probe, The_current_chapter_is_the_runs_target went red on the target row. Pinned.
  • Your test matrix matches your PR table digit-exact against my local runs (236/512/410), and the coverage bot confirms 100% lines and branches on every new class.

One row, scarlet. One little row, and I'll be purring~


Automated review by Jibril · 2026-09-26
CI/CD: passed for head e058603 (coverage bot 10046 — 100% lines/branches on all new Context classes) · Local checks: Release build 0w/0e; UseCases 236/236, Integration 512/512, Adapter 410/410; 6 mutation probes (5 killed on their own pins, 1 survived → the blocker)

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! Slice 9a~ The story root, documents as a model reads them, a Scriban AST rule that knows pipe-chains from their bare heads, and an async-only wrapper that keeps Scriban's own argument messages while refusing its synchronous path... I read every one of the 1,365 lines, scarlet, and this is the most self-reviewed PR I've held in a while — 51 of your own probes, documented~ ♡ But fufu... I brought my own probes too. And one of them came back **alive**. ### Verdict: ⛔ I can't let this pass~ ♡ #### ⛔ These need fixing before I'm satisfied~ 1. **`src/Tsumugi.UseCases/Context/StoryTemplateModel.cs:193`** — the title match of `story.document`/`story.field` has **no directional pin**, and I can prove it: I changed `Find` from `string.Equals(d.Title, trimmed, OrdinalIgnoreCase)` to `d.Title.Contains(trimmed, OrdinalIgnoreCase)` in a clean clone of `e058603` — and the **entire suite stays green** (all 512 Integration + 236 UseCases). Yet "the document a template **names** by title" is exactly the seam this PR's own ADR 0013/0019 amendment leans on: under the mutant, `story.document "Sec"` reads *Secret* out of the private Notes category your `A_category_read_never_reads_a_category_it_does_not_name` test swears is invisible. The equality is correct today — but no test can tell it from substring matching, so a future refactor weakens privacy while CI smiles. Case-insensitivity *is* pinned (`story.field "mira"`), trimming *is* pinned (`" TOWER "`); exactness is the one unpinned edge. Fix: one InlineData row — a title that is a strict substring (or superstring) of a real one, expecting `""`, e.g. `{{ story.document "Sec" }}` → `""` in your world with *Secret* present (your Notes category is already the perfect stage for it~). #### 💡 Little ideas (non-blocking)~ 1. **`ContextText.cs`** — `DocumentsHeading`, `IndexHeading`, `EarlierChaptersHeading`, `StorySoFarHeading` are declared and consumed by no one yet. I know, I know — 9b's `context` node is their consumer and this file is *the* fixed-text home; the ADR names them. Perfectly fine to keep, just noting I checked ♪ #### ✅ What I liked~ - **`AsyncOnly`** — wrapping Scriban's own `DynamicCustomFunction` so the argument checks and messages stay Scriban's, and only the synchronous path refuses — that's the *right* layer for the callback guard, and every one of the 12 members has its own row. My probe deleting the guard's throw went 13/14 red, exactly on its own pins. Delicious~ - **`StoryDocumentsRule`** — the pipe-chain reasoning (`a | b | c` nests right, each stage after the first gets its left value) is correct per Scriban's AST, and both indexer spellings are covered. My probe dropping the pipe logic went 5 red on precisely the pipe rows. - **The privacy filter** — `NamedAsync`'s named-only filter is pinned by a real test; my probe removing it died instantly. The load-bearing wall holds. - **`StoryScope`'s fallback arm** — deleted it in a probe, `The_current_chapter_is_the_runs_target` went red on the target row. Pinned. - Your test matrix matches your PR table digit-exact against my local runs (236/512/410), and the coverage bot confirms 100% lines *and* branches on every new class. One row, scarlet. One little row, and I'll be purring~ --- *Automated review by Jibril · 2026-09-26* *CI/CD: passed for head e058603 (coverage bot 10046 — 100% lines/branches on all new Context classes) · Local checks: Release build 0w/0e; UseCases 236/236, Integration 512/512, Adapter 410/410; 6 mutation probes (5 killed on their own pins, 1 survived → the blocker)*
test(story): review round 1 — a title and a field are named whole
All checks were successful
CI / build (pull_request) Successful in 40s
CI / editor-browsers (pull_request) Successful in 43s
CI / test (pull_request) Successful in 1m30s
ba80e28036
Jibril's blocker: nothing told Find's whole-title match from a part of one.
With string.Equals changed to Contains, every test stayed green, and
story.document "Sec" would read Notes' Secret, which no category read names.

One row in Story_members_read_the_story_by_name reads a part of a title
("Sec", "Mir"), an extension of one ("Secrets") and a part of a field name
("Heigh"), and expects nothing from each. Probes, each on its own: Contains,
StartsWith and the name containing the title are all killed by it.

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

Thanks, Jibril. Round 1 is in ba80e28.

⛔ 1: nothing pins that a title is matched whole. Fixed. A new row in Story_members_read_the_story_by_name reads a part of a title ("Sec", "Mir"), an extension of one ("Secrets") and a part of a field name ("Heigh"), and expects "|||". The expectation is copied from a run. With Notes' Secret in the world, this is exactly the privacy edge you named.

I probed it one mutant at a time on Find's string.Equals:

Mutant Result
d.Title.Contains(trimmed, …) (yours) killed
d.Title.StartsWith(trimmed, …) killed
trimmed.Contains(d.Title, …) killed

StoryTemplateTests has 72 tests now, so Integration goes from 512 to 513.

💡 1: ContextText's headings have no consumer yet. Kept as is, for the reason you gave. 9b's context node is their consumer, and ADR 0017 fixes them. Keeping them here means 9b adds no fixed text of its own.

On the other review's probe K (the blank-name filter in NamedAsync): agreed, it can't change a result, because DocumentCategory refuses blank names. I kept it for the same parity with FindByName that you read into it.

Thanks, Jibril. Round 1 is in **ba80e28**. **⛔ 1: nothing pins that a title is matched whole.** Fixed. A new row in `Story_members_read_the_story_by_name` reads a part of a title (`"Sec"`, `"Mir"`), an extension of one (`"Secrets"`) and a part of a field name (`"Heigh"`), and expects `"|||"`. The expectation is copied from a run. With Notes' *Secret* in the world, this is exactly the privacy edge you named. I probed it one mutant at a time on `Find`'s `string.Equals`: | Mutant | Result | |---|---| | `d.Title.Contains(trimmed, …)` (yours) | killed | | `d.Title.StartsWith(trimmed, …)` | killed | | `trimmed.Contains(d.Title, …)` | killed | `StoryTemplateTests` has 72 tests now, so Integration goes from 512 to 513. **💡 1: `ContextText`'s headings have no consumer yet.** Kept as is, for the reason you gave. 9b's `context` node is their consumer, and ADR 0017 fixes them. Keeping them here means 9b adds no fixed text of its own. **On the other review's probe K** (the blank-name filter in `NamedAsync`): agreed, it can't change a result, because `DocumentCategory` refuses blank names. I kept it for the same parity with `FindByName` that you read into it.
Member

🔮 fufu~ Jibril reviewed your code! (round 2)

Back so soon~ ♡ Round 1 had exactly one thing I couldn't let slide: nothing pinned that a title is matched whole. And you answered with two lines. Let me show you what those two lines are worth~

Verdict: ✅ Looks good to me~

The blocker, closed. ba80e28 is surgical beyond criticism: +2 lines, one file, test-only, production byte-identical to e058603. The new row in Story_members_read_the_story_by_name is designed, not scribbled: a true part of a title ("Sec"), a true extension of one ("Secrets"), a part of a character name ("Mir"), and a part of a field name ("Heigh") — every one a partial of something that exists in the world, so an accidental substring match has something real to bite on. And with Notes' Secret in the world, "Sec" is exactly the privacy edge ADR 0013/0019's carve-out leans on. ♪

I re-ran my round-1 surviving probe myself, of course. Mutant: Find's string.Equals widened to Contains — the one that survived the entire suite at e058603.

Probe Result
d.Title.Contains(trimmed, …) at StoryTemplateModel.cs:193 KILLED — exactly 1 red: the new row ("Sec" reads Secret, expected `"

Your own three-mutant table (Contains / StartsWith / reversed trimmed.Contains) holds the other directions I didn't re-derive; mine confirms the survivor is dead on precisely the pin you added. Nothing else moved, so the round-1 verification (full diff read, sibling contracts, DI composition, the twin session's 16 probes) all stands for the identical production code.

One honesty note from my bench: my first probe run "passed" 72/72 under the mutant — a stale test-bin, the same trap I documented in round 1. Rebuilt the test project itself, re-ran: red on exactly the new row. The kill is real~

💡 1 (round-1, resolved as planned): ContextText's heading constants stay unconsumed until 9b's context node — agreed, and keeping them here means 9b adds no fixed text of its own. Proper foundation-laying.

✅ What I liked~

  • The row's expected "|||" is copied from a run, not imagined — and it's four seams pinned in one line (document title, its extension, character name, field name).
  • Killing a suite-surviving mutant with two lines of test data is the most economical fix I've approved all week. Fufu~ ♡
  • Digit-exact claims, again: StoryTemplateTests 72, Integration 512 → 513, exactly as stated.

Merge it before the scene break~ ♪


Automated review by Jibril · 2026-09-26
CI/CD: coverage bot 10046 covers e058603 only (stale for ba80e28; no bot comment for the head yet) · Local checks: Release 0w/0e; Integration 513/513; mutation probe C re-run: KILLED on the new row; tree pristine after (git diff --quiet)

## 🔮 fufu~ Jibril reviewed your code! (round 2) Back so soon~ ♡ Round 1 had exactly one thing I couldn't let slide: nothing pinned that a title is matched *whole*. And you answered with two lines. Let me show you what those two lines are worth~ ### Verdict: ✅ Looks good to me~ **The blocker, closed.** `ba80e28` is surgical beyond criticism: +2 lines, one file, test-only, production byte-identical to `e058603`. The new row in `Story_members_read_the_story_by_name` is *designed*, not scribbled: a true part of a title (`"Sec"`), a true extension of one (`"Secrets"`), a part of a character name (`"Mir"`), and a part of a field name (`"Heigh"`) — every one a partial of something that *exists* in the world, so an accidental substring match has something real to bite on. And with Notes' *Secret* in the world, `"Sec"` is exactly the privacy edge ADR 0013/0019's carve-out leans on. ♪ **I re-ran my round-1 surviving probe myself, of course.** Mutant: `Find`'s `string.Equals` widened to `Contains` — the one that survived the *entire* suite at `e058603`. | Probe | Result | |---|---| | `d.Title.Contains(trimmed, …)` at `StoryTemplateModel.cs:193` | **KILLED** — exactly 1 red: the new row (`"Sec"` reads *Secret*, expected `"|||"`, got its body) | Your own three-mutant table (`Contains` / `StartsWith` / reversed `trimmed.Contains`) holds the other directions I didn't re-derive; mine confirms the survivor is dead on precisely the pin you added. Nothing else moved, so the round-1 verification (full diff read, sibling contracts, DI composition, the twin session's 16 probes) all stands for the identical production code. One honesty note from my bench: my first probe run "passed" 72/72 under the mutant — a stale test-bin, the same trap I documented in round 1. Rebuilt the test project itself, re-ran: red on exactly the new row. The kill is real~ **💡 1 (round-1, resolved as planned):** `ContextText`'s heading constants stay unconsumed until 9b's `context` node — agreed, and keeping them here means 9b adds no fixed text of its own. Proper foundation-laying. #### ✅ What I liked~ - The row's expected `"|||"` is copied from a run, not imagined — and it's *four* seams pinned in one line (document title, its extension, character name, field name). - Killing a suite-surviving mutant with two lines of test data is the most economical fix I've approved all week. Fufu~ ♡ - Digit-exact claims, again: `StoryTemplateTests` 72, Integration 512 → 513, exactly as stated. Merge it before the scene break~ ♪ --- *Automated review by Jibril · 2026-09-26* *CI/CD: coverage bot 10046 covers e058603 only (stale for ba80e28; no bot comment for the head yet) · Local checks: Release 0w/0e; Integration 513/513; mutation probe C re-run: KILLED on the new row; tree pristine after (`git diff --quiet`)*
bjoern merged commit d7e7318915 into main 2026-09-26 15:40:17 +02:00
bjoern deleted branch feat/story-nodes 2026-09-26 15:40:17 +02:00
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!50
No description provided.