fix: backups — an archive that provably restores, a failed nightly shown, no snapshot left behind #47

Merged
bjoern merged 3 commits from fix/backups-hardening into main 2026-09-26 14:13:39 +02:00
Member

Summary

The backups get harder to lose: an archive now provably restores, a failed nightly shows on the Backups tab, and a snapshot that fails leaves no file behind (ADR 0026 amended).

  • A restored archive starts. The archive's tsumugi.db is a VACUUM INTO copy, in rollback-journal mode. EF first looks for the database over a read-only connection, where the pragma interceptor's switch to WAL is a write SQLite refuses ("attempt to write a readonly database"), so until now a restore stopped the start. PreMigrationBackup.MigrateAsync opens the database read-write once before anything reads it, and the switch stays in the file.
  • Restore round trip (BackupRestoreTests, new). The real host backs up a story, a stored key and its keyring. The ADR 0026 steps restore the archive onto a fresh data root, and a second host lists the story, decrypts the key and accepts the first host's sign-in cookie. Beside it is a golden keyring: a checked-in, test-only DataProtection key and a ciphertext made under application name Tsumugi and purpose Tsumugi.AppSettings, which the booted host must decrypt. Renaming either, or moving the keyring, now fails a test.
  • Owner-only files. The zip and its sidecar are created 0600 through UnixCreateMode when they are opened. There is never a chmod afterwards, which the NAS volume may refuse. The sidecar is pinned byte for byte to the sha256sum -c form, with no BOM. The snapshot copy is created empty and 0600 before VACUUM INTO fills it.
  • No snapshot left behind. A download snapshot that fails, before the copy or midway through it, or is cancelled deletes its temp file. The store has a SnapshotPath seam, like the writer's.
  • The job never runs a slot early. System timers wake a millisecond or two before their wall time. The archive would then be stamped 02:59:59 for the 03:00 slot, so it would not cover that slot, and the next start would take a second archive. The scheduler now waits out the rest.
  • Failed nightly shown. The store keeps the last failed cycle in memory until one succeeds: when it failed and why, for a refusal and also for a crash it still throws. It also reports the slot the nightly job last owed, in the host's zone and date (IBackupStore.JobState). The job and every circuit share this one store, and a test pins that. The last backup's dot on the tab turns to a warning, with one line saying why, in two cases: the last cycle failed, or the newest archive is older than the last slot. A refused click re-reads the store and gives its reason once, in its own alert.
  • Back up now returns early while its own run is in flight. A second click that left the browser before the re-render disabled the button no longer writes a second archive.
  • Snapshot download name. DownloadDatabaseSnapshotTests (new) pins the name to the UTC clock, using a fake clock in a +09:00 zone.

Docs: ADR 0026 amended (Status line, the artifact, the job, the tab, the download, restore, Consequences). New deploy/RESTORE.md runbook for the compose stack; the tab's hint points to it. No migration, no journaled entity, nothing to seed.

Verification

  • Full affected suites green: UseCases 156, BlazorAdapter 361, Integration 384. The full Integration suite then ran 3 more times. The backup and pre-migration integration tests (52) and the Backups adapter tests (15) ran 10 times each. No flakes.
  • New tests:
    • the restore round trip and the golden keyring;
    • the owner-only archive, sidecar and snapshot, which stand down under a umask that already hides the difference;
    • the sidecar's exact bytes;
    • a snapshot that fails midway (a corrupt page), fails before the copy, or is cancelled;
    • a timer that fires 2 ms early;
    • the last failure kept until a success, for a refusal and for a crash;
    • the last slot in Berlin, including past local midnight, when the UTC date lags;
    • the job and the circuits sharing one store;
    • on the tab: the missed-slot line at 23:30, an archive at the slot covering it, the refusal line with its own time, a successful run clearing both warnings, a refused click turning the dot with one alert, a store without a job staying green, and the double-click guard;
    • the download's UTC name.
  • Mutation probes: 55 in all, each caught by its own test. The one survivor left is the review's .backups__warning margin, a CSS value, which the repo does not pin.
    • The first pass ran 37: WAL return, app name, purpose, keys prefix, keyring left out, three ways to lose the create mode, the pre-created target, cleanup, failure clock/reason/clear, .Next for .Last, the UTC zone, the tab's dot/alerts/reducers, the guard, and the download's clock and format.
    • The review round added 18:
      • a single Task.Delay in the scheduler;
      • no crash catch, and the crash recorded with the wall clock;
      • GetUtcNow in JobState;
      • a BOM on the sidecar;
      • no cleanup, and no pre-create together with no cleanup;
      • no pre-create, and no create mode;
      • hh:mm in the missed-slot line;
      • the refusal line dated by the newest archive;
      • OnCompleted keeping a stale Behind;
      • no re-read after a refused click, and the re-read in place of the failure;
      • the warning lines shown beside a click's alert;
      • BackupJobState.None at MaxValue;
      • the runbook pointer dropped;
      • AddScoped for the store.
  • Independent review: two reviewers, verdict fix-then-ready, no blockers. What they found, and how it was fixed:
    • a scheduler timer waking early, which would put a false "missed" warning on the tab on most nights: the loop now waits out the rest, with a test on early-firing timers;
    • five surviving mutations (GetUtcNow in JobState, the refusal line's date, a stale Behind after success, hh:mm, a BOM sidecar): each now has a killing test;
    • after a refused click the dot stayed green, against the ADR: the click now re-reads, and its reason shows once;
    • RESTORE.md wrongly said an older image will not start (EF refuses nothing): reworded;
    • the cleanup test passed on the old code: a mid-copy failure was added;
    • crashes were not recorded: they now are, and are still thrown;
    • the singleton that the tab's warning relies on was unpinned: now pinned;
    • umask-sensitive mode tests: they now self-check the umask;
    • the ADR wording on test hosts, the runbook being "run", and the guard's reach: fixed.
  • Browser pass, before the review round (headless Chromium, seeded dev app on its own port and process group, stopped and cleaned afterwards):
    • an old archive showed the missed-slot warning;
    • a double-click on Back up now wrote exactly one archive, whose .zip, .sha256 and zip entries were -rw-------, and sha256sum -c said OK;
    • with the backups directory made read-only, one refusal alert, and after a reload the dated failure line with a warning dot.
    • The review round's tab change (the re-read after a refused click, and one alert) is pinned through the real Fluxor pipeline in bUnit.

Follow-ups, outside this arc's paths:

  • IBackupStore.JobState has a default member only so PreMigrationBackupTests.FailingStore compiles. Give FailingStore its own JobState and drop the default.
  • SqlitePragmaInterceptor should skip journal_mode on read-only connections. The WAL return then moves out of PreMigrationBackup.
  • Once the gate arc's GatedAppFactory { DataPath } merges, replace BackupRestoreTests' private factory with it.
  • Double-click guards for the stories, documents and chapters create buttons belong to those arcs.
  • A danger alert from a refused click stays until the next click, even after a later success, as before this change.

🤖 Generated with Claude Code

## Summary The backups get harder to lose: an archive now provably restores, a failed nightly shows on the Backups tab, and a snapshot that fails leaves no file behind (ADR 0026 amended). - **A restored archive starts.** The archive's `tsumugi.db` is a `VACUUM INTO` copy, in rollback-journal mode. EF first looks for the database over a read-only connection, where the pragma interceptor's switch to WAL is a write SQLite refuses ("attempt to write a readonly database"), so until now a restore stopped the start. `PreMigrationBackup.MigrateAsync` opens the database read-write once before anything reads it, and the switch stays in the file. - **Restore round trip** (`BackupRestoreTests`, new). The real host backs up a story, a stored key and its keyring. The ADR 0026 steps restore the archive onto a fresh data root, and a second host lists the story, decrypts the key and accepts the first host's sign-in cookie. Beside it is a **golden keyring**: a checked-in, test-only DataProtection key and a ciphertext made under application name `Tsumugi` and purpose `Tsumugi.AppSettings`, which the booted host must decrypt. Renaming either, or moving the keyring, now fails a test. - **Owner-only files.** The zip and its sidecar are created 0600 through `UnixCreateMode` when they are opened. There is never a chmod afterwards, which the NAS volume may refuse. The sidecar is pinned byte for byte to the `sha256sum -c` form, with no BOM. The snapshot copy is created empty and 0600 before `VACUUM INTO` fills it. - **No snapshot left behind.** A download snapshot that fails, before the copy or midway through it, or is cancelled deletes its temp file. The store has a `SnapshotPath` seam, like the writer's. - **The job never runs a slot early.** System timers wake a millisecond or two before their wall time. The archive would then be stamped 02:59:59 for the 03:00 slot, so it would not cover that slot, and the next start would take a second archive. The scheduler now waits out the rest. - **Failed nightly shown.** The store keeps the last failed cycle in memory until one succeeds: when it failed and why, for a refusal and also for a crash it still throws. It also reports the slot the nightly job last owed, in the host's zone and date (`IBackupStore.JobState`). The job and every circuit share this one store, and a test pins that. The last backup's dot on the tab turns to a warning, with one line saying why, in two cases: the last cycle failed, or the newest archive is older than the last slot. A refused click re-reads the store and gives its reason once, in its own alert. - **Back up now** returns early while its own run is in flight. A second click that left the browser before the re-render disabled the button no longer writes a second archive. - **Snapshot download name.** `DownloadDatabaseSnapshotTests` (new) pins the name to the UTC clock, using a fake clock in a +09:00 zone. Docs: ADR 0026 amended (Status line, the artifact, the job, the tab, the download, restore, Consequences). New `deploy/RESTORE.md` runbook for the compose stack; the tab's hint points to it. No migration, no journaled entity, nothing to seed. ## Verification - Full affected suites green: UseCases 156, BlazorAdapter 361, Integration 384. The full Integration suite then ran 3 more times. The backup and pre-migration integration tests (52) and the Backups adapter tests (15) ran 10 times each. No flakes. - New tests: - the restore round trip and the golden keyring; - the owner-only archive, sidecar and snapshot, which stand down under a umask that already hides the difference; - the sidecar's exact bytes; - a snapshot that fails midway (a corrupt page), fails before the copy, or is cancelled; - a timer that fires 2 ms early; - the last failure kept until a success, for a refusal and for a crash; - the last slot in Berlin, including past local midnight, when the UTC date lags; - the job and the circuits sharing one store; - on the tab: the missed-slot line at 23:30, an archive at the slot covering it, the refusal line with its own time, a successful run clearing both warnings, a refused click turning the dot with one alert, a store without a job staying green, and the double-click guard; - the download's UTC name. - Mutation probes: 55 in all, each caught by its own test. The one survivor left is the review's `.backups__warning` margin, a CSS value, which the repo does not pin. - The first pass ran 37: WAL return, app name, purpose, keys prefix, keyring left out, three ways to lose the create mode, the pre-created target, cleanup, failure clock/reason/clear, `.Next` for `.Last`, the UTC zone, the tab's dot/alerts/reducers, the guard, and the download's clock and format. - The review round added 18: - a single `Task.Delay` in the scheduler; - no crash catch, and the crash recorded with the wall clock; - `GetUtcNow` in `JobState`; - a BOM on the sidecar; - no cleanup, and no pre-create together with no cleanup; - no pre-create, and no create mode; - `hh:mm` in the missed-slot line; - the refusal line dated by the newest archive; - `OnCompleted` keeping a stale `Behind`; - no re-read after a refused click, and the re-read in place of the failure; - the warning lines shown beside a click's alert; - `BackupJobState.None` at `MaxValue`; - the runbook pointer dropped; - `AddScoped` for the store. - Independent review: two reviewers, verdict fix-then-ready, no blockers. What they found, and how it was fixed: - a scheduler timer waking early, which would put a false "missed" warning on the tab on most nights: the loop now waits out the rest, with a test on early-firing timers; - five surviving mutations (`GetUtcNow` in `JobState`, the refusal line's date, a stale `Behind` after success, `hh:mm`, a BOM sidecar): each now has a killing test; - after a refused click the dot stayed green, against the ADR: the click now re-reads, and its reason shows once; - `RESTORE.md` wrongly said an older image will not start (EF refuses nothing): reworded; - the cleanup test passed on the old code: a mid-copy failure was added; - crashes were not recorded: they now are, and are still thrown; - the singleton that the tab's warning relies on was unpinned: now pinned; - umask-sensitive mode tests: they now self-check the umask; - the ADR wording on test hosts, the runbook being "run", and the guard's reach: fixed. - Browser pass, before the review round (headless Chromium, seeded dev app on its own port and process group, stopped and cleaned afterwards): - an old archive showed the missed-slot warning; - a double-click on Back up now wrote exactly one archive, whose `.zip`, `.sha256` and zip entries were `-rw-------`, and `sha256sum -c` said OK; - with the backups directory made read-only, one refusal alert, and after a reload the dated failure line with a warning dot. - The review round's tab change (the re-read after a refused click, and one alert) is pinned through the real Fluxor pipeline in bUnit. Follow-ups, outside this arc's paths: - `IBackupStore.JobState` has a default member only so `PreMigrationBackupTests.FailingStore` compiles. Give `FailingStore` its own `JobState` and drop the default. - `SqlitePragmaInterceptor` should skip `journal_mode` on read-only connections. The WAL return then moves out of `PreMigrationBackup`. - Once the gate arc's `GatedAppFactory { DataPath }` merges, replace `BackupRestoreTests`' private factory with it. - Double-click guards for the stories, documents and chapters create buttons belong to those arcs. - A danger alert from a refused click stays until the next click, even after a later success, as before this change. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(backups): an archive that provably restores, a failed nightly shown, no snapshot left behind — a restored database starts, owner-only files, the tab's warning, a slot never run early, a golden keyring
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
8dff8742f3
- A restored archive now starts: a VACUUM INTO copy is in rollback-journal mode, and EF's read-only
  existence check refused the pragma interceptor's switch to WAL ("attempt to write a readonly
  database"). PreMigrationBackup.MigrateAsync opens the database read-write once first, which makes
  the switch for good.
- BackupRestoreTests: the real host backs up a story, a stored key and its keyring; the ADR 0026
  steps restore the archive onto a fresh data root; a second host lists the story, decrypts the key
  and accepts the first host's sign-in cookie. Beside it a golden keyring — a checked-in, test-only
  DataProtection key and a ciphertext made under "Tsumugi" / "Tsumugi.AppSettings" — the booted
  host must decrypt, so renaming the application, the purpose or moving the keyring fails a test.
- The zip and its sidecar are created owner-only (UnixCreateMode 0600 at open, never a chmod the
  NAS volume may refuse), the sidecar byte for byte the `sha256sum -c` form; the snapshot copy is
  created empty and owner-only before VACUUM INTO fills it, so the archive's tsumugi.db entry is
  0600 too. The owner-only tests stand down under a umask that already hides the difference.
- A download snapshot that fails — before the copy or midway through it — or is cancelled deletes
  its temp file (a SnapshotPath seam on the store, as on the writer).
- The job never runs a slot early: a timer that wakes a hair before its wall time waits out the
  rest, or its archive, stamped 02:59:59, would not cover the 03:00 slot.
- The store keeps the last failed cycle (when, why) in memory until one succeeds — a refusal, or a
  crash it still throws — and reports the slot the nightly job last owed, in the host's zone and
  date (IBackupStore.JobState); a test pins that the job and every circuit share the one store. The
  Backups tab turns the last backup's dot to a warning and says why when the last cycle failed or
  the newest archive is older than the last slot; a refused click re-reads the store and says why
  once, in its own alert.
- Back up now returns early while a run is in flight: a second click that left the browser before
  the re-render disabled the button starts no second archive.
- DownloadDatabaseSnapshotTests pins the download's name to the UTC clock on a fake clock in a
  +09:00 zone.
- deploy/RESTORE.md walks the restore for the compose stack; ADR 0026 amended.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
bjoern referenced this pull request from a commit 2026-09-26 13:34:22 +02:00
docs: backups hardening's PR is #47
All checks were successful
CI / build (pull_request) Successful in 37s
CI / editor-browsers (pull_request) Successful in 38s
CI / test (pull_request) Successful in 1m20s
80f2babf7a
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: b9cd453 · Generated: 2026-09-26 11:55:58 UTC · Revision: #2

Summary

Summary
Generated on: 09/26/2026 - 11:55:58
Coverage date: 09/26/2026 - 11:55:38 - 09/26/2026 - 11:55:55
Parser: MultiReport (6x Cobertura)
Assemblies: 9
Classes: 487
Files: 441
Line coverage: 97.8% (14770 of 15089)
Covered lines: 14770
Uncovered lines: 319
Coverable lines: 15089
Total lines: 32326
Branch coverage: 93.7% (5647 of 6024)
Covered branches: 5647
Total branches: 6024
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 - 95.6%
Name Line Branch
Tsumugi.BlazorAdapter 95.6% 90.6%
Tsumugi.BlazorAdapter.BlazorAdapterAssembly 100%
Tsumugi.BlazorAdapter.Documents.CategoryCreated 100%
Tsumugi.BlazorAdapter.Documents.CategoryEditor 91.8% 87.2%
Tsumugi.BlazorAdapter.Documents.CategorySaved 100%
Tsumugi.BlazorAdapter.Documents.ChangeCategoryFieldsRequested 100%
Tsumugi.BlazorAdapter.Documents.CreateCategoryRequested 100%
Tsumugi.BlazorAdapter.Documents.CreateDocumentRequested 100%
Tsumugi.BlazorAdapter.Documents.DeleteCategoryChoice 100%
Tsumugi.BlazorAdapter.Documents.DeleteCategoryDialog 100% 94.1%
Tsumugi.BlazorAdapter.Documents.DeleteCategoryRequested 100%
Tsumugi.BlazorAdapter.Documents.DeleteDocumentRequested 100%
Tsumugi.BlazorAdapter.Documents.DocumentAutoSaveFailed 100%
Tsumugi.BlazorAdapter.Documents.DocumentCreated 100%
Tsumugi.BlazorAdapter.Documents.DocumentEditor 95.1% 88.8%
Tsumugi.BlazorAdapter.Documents.DocumentImageRefused 100%
Tsumugi.BlazorAdapter.Documents.DocumentImageSet 100%
Tsumugi.BlazorAdapter.Documents.DocumentsActionFailed 100%
Tsumugi.BlazorAdapter.Documents.DocumentSaved 100%
Tsumugi.BlazorAdapter.Documents.DocumentsEffects 100% 100%
Tsumugi.BlazorAdapter.Documents.DocumentsList 98.2% 92.4%
Tsumugi.BlazorAdapter.Documents.DocumentsLoaded 100%
Tsumugi.BlazorAdapter.Documents.DocumentsPage 100% 100%
Tsumugi.BlazorAdapter.Documents.DocumentsReducers 100% 100%
Tsumugi.BlazorAdapter.Documents.DocumentsRefusal 100%
Tsumugi.BlazorAdapter.Documents.DocumentsState 100% 100%
Tsumugi.BlazorAdapter.Documents.FieldInput 100% 96.6%
Tsumugi.BlazorAdapter.Documents.FieldsChanges 100% 100%
Tsumugi.BlazorAdapter.Documents.FieldShapes 90% 87.5%
Tsumugi.BlazorAdapter.Documents.ImageUpload 100%
Tsumugi.BlazorAdapter.Documents.ImageUploadRefusal 100%
Tsumugi.BlazorAdapter.Documents.ImpactText 100% 94.2%
Tsumugi.BlazorAdapter.Documents.LoadDocuments 100%
Tsumugi.BlazorAdapter.Documents.MoveDocumentChoice 100%
Tsumugi.BlazorAdapter.Documents.MoveDocumentDialog 95.6% 71.4%
Tsumugi.BlazorAdapter.Documents.MoveDocumentsRequested 100%
Tsumugi.BlazorAdapter.Documents.ReorderCategoriesRequested 100%
Tsumugi.BlazorAdapter.Documents.SetCategoryCollapsed 100%
Tsumugi.BlazorAdapter.Documents.SetDocumentImageRequested 100%
Tsumugi.BlazorAdapter.Documents.SetDocumentsFilter 100%
Tsumugi.BlazorAdapter.Documents.UpdateCategoryRequested 100%
Tsumugi.BlazorAdapter.Documents.UpdateDocumentRequested 100%
Tsumugi.BlazorAdapter.Graphs.GraphCanvas 37.5% 25%
Tsumugi.BlazorAdapter.Graphs.GraphPlaygroundPage 20% 25%
Tsumugi.BlazorAdapter.Notifications.DomainChangedBridge 94.4% 83.3%
Tsumugi.BlazorAdapter.Notifications.DomainChangesReceived 75% 100%
Tsumugi.BlazorAdapter.Runs.RunChangedBridge 97.1% 90.6%
Tsumugi.BlazorAdapter.Runs.RunChangesReceived 100%
Tsumugi.BlazorAdapter.Runs.RunTextReceived 100%
Tsumugi.BlazorAdapter.Settings.BackupCompleted 100%
Tsumugi.BlazorAdapter.Settings.BackupFailed 100%
Tsumugi.BlazorAdapter.Settings.BackupNowRequested 100%
Tsumugi.BlazorAdapter.Settings.BackupSettingsLoaded 100%
Tsumugi.BlazorAdapter.Settings.BackupsSection 100% 100%
Tsumugi.BlazorAdapter.Settings.BackupsSettingsEffects 100% 100%
Tsumugi.BlazorAdapter.Settings.BackupsSettingsReducers 100%
Tsumugi.BlazorAdapter.Settings.BackupsSettingsState 100%
Tsumugi.BlazorAdapter.Settings.DefaultsSection 100% 100%
Tsumugi.BlazorAdapter.Settings.DefaultsSettingsEffects 100% 100%
Tsumugi.BlazorAdapter.Settings.DefaultsSettingsReducers 100%
Tsumugi.BlazorAdapter.Settings.DefaultsSettingsState 100%
Tsumugi.BlazorAdapter.Settings.LlmDefaultsLoaded 100%
Tsumugi.BlazorAdapter.Settings.LlmDefaultsSaved 100%
Tsumugi.BlazorAdapter.Settings.LlmDefaultsSaveFailed 100%
Tsumugi.BlazorAdapter.Settings.LoadBackupSettings 100%
Tsumugi.BlazorAdapter.Settings.LoadProviderSettings 100%
Tsumugi.BlazorAdapter.Settings.ModelCatalogLoaded 100%
Tsumugi.BlazorAdapter.Settings.ModelCatalogLoadFailed 100%
Tsumugi.BlazorAdapter.Settings.OpenRouterKeySaved 100%
Tsumugi.BlazorAdapter.Settings.OpenRouterKeySaveFailed 100%
Tsumugi.BlazorAdapter.Settings.ProviderKeyChecked 100%
Tsumugi.BlazorAdapter.Settings.ProviderSection 100% 100%
Tsumugi.BlazorAdapter.Settings.ProviderSettingsEffects 100% 100%
Tsumugi.BlazorAdapter.Settings.ProviderSettingsLoaded 100%
Tsumugi.BlazorAdapter.Settings.ProviderSettingsReducers 100% 100%
Tsumugi.BlazorAdapter.Settings.ProviderSettingsState 100% 100%
Tsumugi.BlazorAdapter.Settings.SaveLlmDefaultsRequested 100%
Tsumugi.BlazorAdapter.Settings.SaveOpenRouterKeyRequested 100%
Tsumugi.BlazorAdapter.Stories.CreateStoryRequested 100%
Tsumugi.BlazorAdapter.Stories.PurgeStoryRequested 100%
Tsumugi.BlazorAdapter.Stories.RestoreStoryRequested 100%
Tsumugi.BlazorAdapter.Stories.SetShowArchived 100%
Tsumugi.BlazorAdapter.Stories.SetStoriesFilter 100%
Tsumugi.BlazorAdapter.Stories.SetStoryArchivedRequested 100%
Tsumugi.BlazorAdapter.Stories.StoriesEffects 100% 100%
Tsumugi.BlazorAdapter.Stories.StoriesLoaded 100%
Tsumugi.BlazorAdapter.Stories.StoriesPage 100% 100%
Tsumugi.BlazorAdapter.Stories.StoriesReducers 100% 100%
Tsumugi.BlazorAdapter.Stories.StoriesState 100% 100%
Tsumugi.BlazorAdapter.Stories.StoryActionFailed 100%
Tsumugi.BlazorAdapter.Stories.StoryCreated 100%
Tsumugi.BlazorAdapter.Stories.StoryCreateFailed 100%
Tsumugi.BlazorAdapter.Stories.StoryTrashPage 100% 100%
Tsumugi.BlazorAdapter.Stories.TrashStoryRequested 100%
Tsumugi.BlazorAdapter.Workspace.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 90% 83.3%
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.3% 67.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 98.2% 97.5%
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.1%
Name Line Branch
Tsumugi.Domain 96.1% 94.6%
System.Text.RegularExpressions.Generated 90.7% 87.5%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F5BB471BFAD87E25
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__DigitsAndSeparators_2
97.4% 95.8%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F5BB471BFAD87E25
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__LineBreak_0
91% 91.1%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F5BB471BFAD87E25
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__NumberToken_1
86.4% 77.7%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F5BB471BFAD87E25
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__Plain_4
91.9% 88.8%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F5BB471BFAD87E25
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__ThousandsGroup_3
90.9% 84.3%
Tsumugi.Domain.Documents.CategoryFields 94.5% 93.7%
Tsumugi.Domain.Documents.ChoiceOption 100%
Tsumugi.Domain.Documents.ChoiceValue 100%
Tsumugi.Domain.Documents.DateText 100%
Tsumugi.Domain.Documents.DateValue 100%
Tsumugi.Domain.Documents.Document 100% 100%
Tsumugi.Domain.Documents.DocumentCategory 100% 100%
Tsumugi.Domain.Documents.DocumentValues 100%
Tsumugi.Domain.Documents.FieldCarry 100% 100%
Tsumugi.Domain.Documents.FieldConversion 100% 100%
Tsumugi.Domain.Documents.FieldDefinition 97% 95.3%
Tsumugi.Domain.Documents.FieldMessages 100% 100%
Tsumugi.Domain.Documents.FieldNames 100%
Tsumugi.Domain.Documents.FieldsChange 100%
Tsumugi.Domain.Documents.FieldShape 95.2% 87.5%
Tsumugi.Domain.Documents.FieldsPlan 100%
Tsumugi.Domain.Documents.FieldsPlanner 99.2% 98.5%
Tsumugi.Domain.Documents.FieldValue 100%
Tsumugi.Domain.Documents.FieldValues 95.4% 97.7%
Tsumugi.Domain.Documents.ImageFiles 100% 100%
Tsumugi.Domain.Documents.ImageValue 100%
Tsumugi.Domain.Documents.MovePlan 100%
Tsumugi.Domain.Documents.NumberParse 100%
Tsumugi.Domain.Documents.NumberText 100% 97.6%
Tsumugi.Domain.Documents.NumberValue 100%
Tsumugi.Domain.Documents.StoryImage 100%
Tsumugi.Domain.Documents.TextValue 100% 100%
Tsumugi.Domain.Documents.ValueChange 100%
Tsumugi.Domain.Documents.ValueChangeKey 100%
Tsumugi.Domain.Documents.ValueImpact 100% 100%
Tsumugi.Domain.Journal.ChangeLogEntry 97.8% 75%
Tsumugi.Domain.Journal.StorySnapshot 95.4% 83.3%
Tsumugi.Domain.Settings.AppSetting 100%
Tsumugi.Domain.Stories.Chapter 100% 100%
Tsumugi.Domain.Stories.InlineMarkup 100% 100%
Tsumugi.Domain.Stories.MarkupRun 100%
Tsumugi.Domain.Stories.Provenance 100% 100%
Tsumugi.Domain.Stories.Segment 100% 100%
Tsumugi.Domain.Stories.Story 100% 100%
Tsumugi.Domain.Text 100% 100%
Tsumugi.Infrastructure - 97.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.5% 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% 86.4%
Program 100% 100%
Tsumugi.Server.Backups.BackupConfiguration 100% 100%
Tsumugi.Server.Components.App 100%
Tsumugi.Server.Components.Layout.AppHeader 100% 50%
Tsumugi.Server.Components.Layout.MainLayout 100%
Tsumugi.Server.Components.Pages.Gate 100% 100%
Tsumugi.Server.Provider.OpenRouterConfiguration 100% 100%
Tsumugi.Server.Security.AccessGate 100% 66.6%
Tsumugi.Server.Security.AccessSecret 100% 50%
Tsumugi.Server.VolumeStartupValidator 100% 100%
Tsumugi.UseCases - 98.2%
Name Line Branch
Tsumugi.UseCases 98.2% 94.6%
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.DependencyInjection 100%
Tsumugi.UseCases.Diagnostics.SeedDevData 98.2% 71.4%
Tsumugi.UseCases.Diagnostics.SeedPictures 100%
Tsumugi.UseCases.Documents.ChangeCategoryFields 100% 83.3%
Tsumugi.UseCases.Documents.CreateCategory 100% 100%
Tsumugi.UseCases.Documents.CreateDocument 100% 100%
Tsumugi.UseCases.Documents.DefaultFields 100% 100%
Tsumugi.UseCases.Documents.DeleteCategory 100% 94.4%
Tsumugi.UseCases.Documents.DeleteDocument 100% 100%
Tsumugi.UseCases.Documents.DocumentCategoryDto 100%
Tsumugi.UseCases.Documents.DocumentCategoryProfile 100%
Tsumugi.UseCases.Documents.DocumentDto 100%
Tsumugi.UseCases.Documents.DocumentPatch 100%
Tsumugi.UseCases.Documents.DocumentProfile 100%
Tsumugi.UseCases.Documents.DocumentsCategory 100%
Tsumugi.UseCases.Documents.DocumentsView 100%
Tsumugi.UseCases.Documents.DocumentWrites 100% 100%
Tsumugi.UseCases.Documents.FieldsChangeLabel 95.4% 88.2%
Tsumugi.UseCases.Documents.GetDocuments 100% 100%
Tsumugi.UseCases.Documents.GetStoryImage 100% 100%
Tsumugi.UseCases.Documents.ImpactPreview 100% 100%
Tsumugi.UseCases.Documents.LossMessages 92.3% 87.5%
Tsumugi.UseCases.Documents.MoveDocuments 100% 100%
Tsumugi.UseCases.Documents.ReorderCategories 100% 100%
Tsumugi.UseCases.Documents.SetDocumentImage 100% 93.7%
Tsumugi.UseCases.Documents.StoryImageFile 100%
Tsumugi.UseCases.Documents.UpdateCategory 100% 100%
Tsumugi.UseCases.Documents.UpdateDocument 100% 81.2%
Tsumugi.UseCases.Journal.GetStoryHistory 100% 100%
Tsumugi.UseCases.Journal.GetUndoStatus 100% 100%
Tsumugi.UseCases.Journal.HistoryOperation 66.6%
Tsumugi.UseCases.Journal.HistorySnapshot 87.5%
Tsumugi.UseCases.Journal.OperationGrouping 100% 100%
Tsumugi.UseCases.Journal.RedoRevert 100% 100%
Tsumugi.UseCases.Journal.RedoRun 75% 57.1%
Tsumugi.UseCases.Journal.RedoTyping 100% 100%
Tsumugi.UseCases.Journal.RevertConflict 100%
Tsumugi.UseCases.Journal.RevertOutcome 100%
Tsumugi.UseCases.Journal.RevertRun 96.5% 77.7%
Tsumugi.UseCases.Journal.RevertToSnapshot 92.3% 83.3%
Tsumugi.UseCases.Journal.StoryHistory 100%
Tsumugi.UseCases.Journal.TakeSnapshot 100% 87.5%
Tsumugi.UseCases.Journal.UndoStatus 100%
Tsumugi.UseCases.Journal.UndoTyping 100% 100%
Tsumugi.UseCases.Journal.UserOperation 100%
Tsumugi.UseCases.Notifications.DomainChanged 100%
Tsumugi.UseCases.Ordering 100% 100%
Tsumugi.UseCases.Runs.RunEvent 100%
Tsumugi.UseCases.Segments.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:** `b9cd453` · **Generated:** 2026-09-26 11:55:58 UTC · **Revision:** #2 # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 09/26/2026 - 11:55:58 | | Coverage date: | 09/26/2026 - 11:55:38 - 09/26/2026 - 11:55:55 | | Parser: | MultiReport (6x Cobertura) | | Assemblies: | 9 | | Classes: | 487 | | Files: | 441 | | **Line coverage:** | 97.8% (14770 of 15089) | | Covered lines: | 14770 | | Uncovered lines: | 319 | | Coverable lines: | 15089 | | Total lines: | 32326 | | **Branch coverage:** | 93.7% (5647 of 6024) | | Covered branches: | 5647 | | Total branches: | 6024 | | **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 - 95.6%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Tsumugi.BlazorAdapter**|**95.6%**|**90.6%**| |Tsumugi.BlazorAdapter.BlazorAdapterAssembly|100%|| |Tsumugi.BlazorAdapter.Documents.CategoryCreated|100%|| |Tsumugi.BlazorAdapter.Documents.CategoryEditor|91.8%|87.2%| |Tsumugi.BlazorAdapter.Documents.CategorySaved|100%|| |Tsumugi.BlazorAdapter.Documents.ChangeCategoryFieldsRequested|100%|| |Tsumugi.BlazorAdapter.Documents.CreateCategoryRequested|100%|| |Tsumugi.BlazorAdapter.Documents.CreateDocumentRequested|100%|| |Tsumugi.BlazorAdapter.Documents.DeleteCategoryChoice|100%|| |Tsumugi.BlazorAdapter.Documents.DeleteCategoryDialog|100%|94.1%| |Tsumugi.BlazorAdapter.Documents.DeleteCategoryRequested|100%|| |Tsumugi.BlazorAdapter.Documents.DeleteDocumentRequested|100%|| |Tsumugi.BlazorAdapter.Documents.DocumentAutoSaveFailed|100%|| |Tsumugi.BlazorAdapter.Documents.DocumentCreated|100%|| |Tsumugi.BlazorAdapter.Documents.DocumentEditor|95.1%|88.8%| |Tsumugi.BlazorAdapter.Documents.DocumentImageRefused|100%|| |Tsumugi.BlazorAdapter.Documents.DocumentImageSet|100%|| |Tsumugi.BlazorAdapter.Documents.DocumentsActionFailed|100%|| |Tsumugi.BlazorAdapter.Documents.DocumentSaved|100%|| |Tsumugi.BlazorAdapter.Documents.DocumentsEffects|100%|100%| |Tsumugi.BlazorAdapter.Documents.DocumentsList|98.2%|92.4%| |Tsumugi.BlazorAdapter.Documents.DocumentsLoaded|100%|| |Tsumugi.BlazorAdapter.Documents.DocumentsPage|100%|100%| |Tsumugi.BlazorAdapter.Documents.DocumentsReducers|100%|100%| |Tsumugi.BlazorAdapter.Documents.DocumentsRefusal|100%|| |Tsumugi.BlazorAdapter.Documents.DocumentsState|100%|100%| |Tsumugi.BlazorAdapter.Documents.FieldInput|100%|96.6%| |Tsumugi.BlazorAdapter.Documents.FieldsChanges|100%|100%| |Tsumugi.BlazorAdapter.Documents.FieldShapes|90%|87.5%| |Tsumugi.BlazorAdapter.Documents.ImageUpload|100%|| |Tsumugi.BlazorAdapter.Documents.ImageUploadRefusal|100%|| |Tsumugi.BlazorAdapter.Documents.ImpactText|100%|94.2%| |Tsumugi.BlazorAdapter.Documents.LoadDocuments|100%|| |Tsumugi.BlazorAdapter.Documents.MoveDocumentChoice|100%|| |Tsumugi.BlazorAdapter.Documents.MoveDocumentDialog|95.6%|71.4%| |Tsumugi.BlazorAdapter.Documents.MoveDocumentsRequested|100%|| |Tsumugi.BlazorAdapter.Documents.ReorderCategoriesRequested|100%|| |Tsumugi.BlazorAdapter.Documents.SetCategoryCollapsed|100%|| |Tsumugi.BlazorAdapter.Documents.SetDocumentImageRequested|100%|| |Tsumugi.BlazorAdapter.Documents.SetDocumentsFilter|100%|| |Tsumugi.BlazorAdapter.Documents.UpdateCategoryRequested|100%|| |Tsumugi.BlazorAdapter.Documents.UpdateDocumentRequested|100%|| |Tsumugi.BlazorAdapter.Graphs.GraphCanvas|37.5%|25%| |Tsumugi.BlazorAdapter.Graphs.GraphPlaygroundPage|20%|25%| |Tsumugi.BlazorAdapter.Notifications.DomainChangedBridge|94.4%|83.3%| |Tsumugi.BlazorAdapter.Notifications.DomainChangesReceived|75%|100%| |Tsumugi.BlazorAdapter.Runs.RunChangedBridge|97.1%|90.6%| |Tsumugi.BlazorAdapter.Runs.RunChangesReceived|100%|| |Tsumugi.BlazorAdapter.Runs.RunTextReceived|100%|| |Tsumugi.BlazorAdapter.Settings.BackupCompleted|100%|| |Tsumugi.BlazorAdapter.Settings.BackupFailed|100%|| |Tsumugi.BlazorAdapter.Settings.BackupNowRequested|100%|| |Tsumugi.BlazorAdapter.Settings.BackupSettingsLoaded|100%|| |Tsumugi.BlazorAdapter.Settings.BackupsSection|100%|100%| |Tsumugi.BlazorAdapter.Settings.BackupsSettingsEffects|100%|100%| |Tsumugi.BlazorAdapter.Settings.BackupsSettingsReducers|100%|| |Tsumugi.BlazorAdapter.Settings.BackupsSettingsState|100%|| |Tsumugi.BlazorAdapter.Settings.DefaultsSection|100%|100%| |Tsumugi.BlazorAdapter.Settings.DefaultsSettingsEffects|100%|100%| |Tsumugi.BlazorAdapter.Settings.DefaultsSettingsReducers|100%|| |Tsumugi.BlazorAdapter.Settings.DefaultsSettingsState|100%|| |Tsumugi.BlazorAdapter.Settings.LlmDefaultsLoaded|100%|| |Tsumugi.BlazorAdapter.Settings.LlmDefaultsSaved|100%|| |Tsumugi.BlazorAdapter.Settings.LlmDefaultsSaveFailed|100%|| |Tsumugi.BlazorAdapter.Settings.LoadBackupSettings|100%|| |Tsumugi.BlazorAdapter.Settings.LoadProviderSettings|100%|| |Tsumugi.BlazorAdapter.Settings.ModelCatalogLoaded|100%|| |Tsumugi.BlazorAdapter.Settings.ModelCatalogLoadFailed|100%|| |Tsumugi.BlazorAdapter.Settings.OpenRouterKeySaved|100%|| |Tsumugi.BlazorAdapter.Settings.OpenRouterKeySaveFailed|100%|| |Tsumugi.BlazorAdapter.Settings.ProviderKeyChecked|100%|| |Tsumugi.BlazorAdapter.Settings.ProviderSection|100%|100%| |Tsumugi.BlazorAdapter.Settings.ProviderSettingsEffects|100%|100%| |Tsumugi.BlazorAdapter.Settings.ProviderSettingsLoaded|100%|| |Tsumugi.BlazorAdapter.Settings.ProviderSettingsReducers|100%|100%| |Tsumugi.BlazorAdapter.Settings.ProviderSettingsState|100%|100%| |Tsumugi.BlazorAdapter.Settings.SaveLlmDefaultsRequested|100%|| |Tsumugi.BlazorAdapter.Settings.SaveOpenRouterKeyRequested|100%|| |Tsumugi.BlazorAdapter.Stories.CreateStoryRequested|100%|| |Tsumugi.BlazorAdapter.Stories.PurgeStoryRequested|100%|| |Tsumugi.BlazorAdapter.Stories.RestoreStoryRequested|100%|| |Tsumugi.BlazorAdapter.Stories.SetShowArchived|100%|| |Tsumugi.BlazorAdapter.Stories.SetStoriesFilter|100%|| |Tsumugi.BlazorAdapter.Stories.SetStoryArchivedRequested|100%|| |Tsumugi.BlazorAdapter.Stories.StoriesEffects|100%|100%| |Tsumugi.BlazorAdapter.Stories.StoriesLoaded|100%|| |Tsumugi.BlazorAdapter.Stories.StoriesPage|100%|100%| |Tsumugi.BlazorAdapter.Stories.StoriesReducers|100%|100%| |Tsumugi.BlazorAdapter.Stories.StoriesState|100%|100%| |Tsumugi.BlazorAdapter.Stories.StoryActionFailed|100%|| |Tsumugi.BlazorAdapter.Stories.StoryCreated|100%|| |Tsumugi.BlazorAdapter.Stories.StoryCreateFailed|100%|| |Tsumugi.BlazorAdapter.Stories.StoryTrashPage|100%|100%| |Tsumugi.BlazorAdapter.Stories.TrashStoryRequested|100%|| |Tsumugi.BlazorAdapter.Workspace.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|90%|83.3%| |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.3%|67.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|98.2%|97.5%| |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.1%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Tsumugi.Domain**|**96.1%**|**94.6%**| |System.Text.RegularExpressions.Generated|90.7%|87.5%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F5BB471BFAD87E25<br/>BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__DigitsAndSeparators_2|97.4%|95.8%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F5BB471BFAD87E25<br/>BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__LineBreak_0|91%|91.1%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F5BB471BFAD87E25<br/>BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__NumberToken_1|86.4%|77.7%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F5BB471BFAD87E25<br/>BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__Plain_4|91.9%|88.8%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F5BB471BFAD87E25<br/>BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__ThousandsGroup_3|90.9%|84.3%| |Tsumugi.Domain.Documents.CategoryFields|94.5%|93.7%| |Tsumugi.Domain.Documents.ChoiceOption|100%|| |Tsumugi.Domain.Documents.ChoiceValue|100%|| |Tsumugi.Domain.Documents.DateText|100%|| |Tsumugi.Domain.Documents.DateValue|100%|| |Tsumugi.Domain.Documents.Document|100%|100%| |Tsumugi.Domain.Documents.DocumentCategory|100%|100%| |Tsumugi.Domain.Documents.DocumentValues|100%|| |Tsumugi.Domain.Documents.FieldCarry|100%|100%| |Tsumugi.Domain.Documents.FieldConversion|100%|100%| |Tsumugi.Domain.Documents.FieldDefinition|97%|95.3%| |Tsumugi.Domain.Documents.FieldMessages|100%|100%| |Tsumugi.Domain.Documents.FieldNames|100%|| |Tsumugi.Domain.Documents.FieldsChange|100%|| |Tsumugi.Domain.Documents.FieldShape|95.2%|87.5%| |Tsumugi.Domain.Documents.FieldsPlan|100%|| |Tsumugi.Domain.Documents.FieldsPlanner|99.2%|98.5%| |Tsumugi.Domain.Documents.FieldValue|100%|| |Tsumugi.Domain.Documents.FieldValues|95.4%|97.7%| |Tsumugi.Domain.Documents.ImageFiles|100%|100%| |Tsumugi.Domain.Documents.ImageValue|100%|| |Tsumugi.Domain.Documents.MovePlan|100%|| |Tsumugi.Domain.Documents.NumberParse|100%|| |Tsumugi.Domain.Documents.NumberText|100%|97.6%| |Tsumugi.Domain.Documents.NumberValue|100%|| |Tsumugi.Domain.Documents.StoryImage|100%|| |Tsumugi.Domain.Documents.TextValue|100%|100%| |Tsumugi.Domain.Documents.ValueChange|100%|| |Tsumugi.Domain.Documents.ValueChangeKey|100%|| |Tsumugi.Domain.Documents.ValueImpact|100%|100%| |Tsumugi.Domain.Journal.ChangeLogEntry|97.8%|75%| |Tsumugi.Domain.Journal.StorySnapshot|95.4%|83.3%| |Tsumugi.Domain.Settings.AppSetting|100%|| |Tsumugi.Domain.Stories.Chapter|100%|100%| |Tsumugi.Domain.Stories.InlineMarkup|100%|100%| |Tsumugi.Domain.Stories.MarkupRun|100%|| |Tsumugi.Domain.Stories.Provenance|100%|100%| |Tsumugi.Domain.Stories.Segment|100%|100%| |Tsumugi.Domain.Stories.Story|100%|100%| |Tsumugi.Domain.Text|100%|100%| </details> <details><summary>Tsumugi.Infrastructure - 97.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.5%|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%**|**86.4%**| |Program|100%|100%| |Tsumugi.Server.Backups.BackupConfiguration|100%|100%| |Tsumugi.Server.Components.App|100%|| |Tsumugi.Server.Components.Layout.AppHeader|100%|50%| |Tsumugi.Server.Components.Layout.MainLayout|100%|| |Tsumugi.Server.Components.Pages.Gate|100%|100%| |Tsumugi.Server.Provider.OpenRouterConfiguration|100%|100%| |Tsumugi.Server.Security.AccessGate|100%|66.6%| |Tsumugi.Server.Security.AccessSecret|100%|50%| |Tsumugi.Server.VolumeStartupValidator|100%|100%| </details> <details><summary>Tsumugi.UseCases - 98.2%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Tsumugi.UseCases**|**98.2%**|**94.6%**| |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.DependencyInjection|100%|| |Tsumugi.UseCases.Diagnostics.SeedDevData|98.2%|71.4%| |Tsumugi.UseCases.Diagnostics.SeedPictures|100%|| |Tsumugi.UseCases.Documents.ChangeCategoryFields|100%|83.3%| |Tsumugi.UseCases.Documents.CreateCategory|100%|100%| |Tsumugi.UseCases.Documents.CreateDocument|100%|100%| |Tsumugi.UseCases.Documents.DefaultFields|100%|100%| |Tsumugi.UseCases.Documents.DeleteCategory|100%|94.4%| |Tsumugi.UseCases.Documents.DeleteDocument|100%|100%| |Tsumugi.UseCases.Documents.DocumentCategoryDto|100%|| |Tsumugi.UseCases.Documents.DocumentCategoryProfile|100%|| |Tsumugi.UseCases.Documents.DocumentDto|100%|| |Tsumugi.UseCases.Documents.DocumentPatch|100%|| |Tsumugi.UseCases.Documents.DocumentProfile|100%|| |Tsumugi.UseCases.Documents.DocumentsCategory|100%|| |Tsumugi.UseCases.Documents.DocumentsView|100%|| |Tsumugi.UseCases.Documents.DocumentWrites|100%|100%| |Tsumugi.UseCases.Documents.FieldsChangeLabel|95.4%|88.2%| |Tsumugi.UseCases.Documents.GetDocuments|100%|100%| |Tsumugi.UseCases.Documents.GetStoryImage|100%|100%| |Tsumugi.UseCases.Documents.ImpactPreview|100%|100%| |Tsumugi.UseCases.Documents.LossMessages|92.3%|87.5%| |Tsumugi.UseCases.Documents.MoveDocuments|100%|100%| |Tsumugi.UseCases.Documents.ReorderCategories|100%|100%| |Tsumugi.UseCases.Documents.SetDocumentImage|100%|93.7%| |Tsumugi.UseCases.Documents.StoryImageFile|100%|| |Tsumugi.UseCases.Documents.UpdateCategory|100%|100%| |Tsumugi.UseCases.Documents.UpdateDocument|100%|81.2%| |Tsumugi.UseCases.Journal.GetStoryHistory|100%|100%| |Tsumugi.UseCases.Journal.GetUndoStatus|100%|100%| |Tsumugi.UseCases.Journal.HistoryOperation|66.6%|| |Tsumugi.UseCases.Journal.HistorySnapshot|87.5%|| |Tsumugi.UseCases.Journal.OperationGrouping|100%|100%| |Tsumugi.UseCases.Journal.RedoRevert|100%|100%| |Tsumugi.UseCases.Journal.RedoRun|75%|57.1%| |Tsumugi.UseCases.Journal.RedoTyping|100%|100%| |Tsumugi.UseCases.Journal.RevertConflict|100%|| |Tsumugi.UseCases.Journal.RevertOutcome|100%|| |Tsumugi.UseCases.Journal.RevertRun|96.5%|77.7%| |Tsumugi.UseCases.Journal.RevertToSnapshot|92.3%|83.3%| |Tsumugi.UseCases.Journal.StoryHistory|100%|| |Tsumugi.UseCases.Journal.TakeSnapshot|100%|87.5%| |Tsumugi.UseCases.Journal.UndoStatus|100%|| |Tsumugi.UseCases.Journal.UndoTyping|100%|100%| |Tsumugi.UseCases.Journal.UserOperation|100%|| |Tsumugi.UseCases.Notifications.DomainChanged|100%|| |Tsumugi.UseCases.Ordering|100%|100%| |Tsumugi.UseCases.Runs.RunEvent|100%|| |Tsumugi.UseCases.Segments.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>
test(backups): the restore test clears only its own hosts' pools — each host's connection string recorded as it starts, and the teardown checks nothing of them stayed pooled
All checks were successful
CI / build (pull_request) Successful in 36s
CI / editor-browsers (pull_request) Successful in 40s
CI / test (pull_request) Successful in 1m19s
b9cd453bbb
A process-wide pool clear races the Integration tests running beside this one, and PR #42's guard
refuses any new file that makes one.

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

🔮 fufu~ Jibril reviewed your code!

Oh? Oh~! An archive that provably restores — you wrote a test that performs the ADR's restore steps on a real archive and boots a second host on the result, cookies and keyring and all. That's not a backup PR, that's a promise with teeth. I read all 1,443 diff lines, every changed file whole, traced the wiring through Program.cs, the pragma interceptor, the compose stack and the Dockerfile — and then I got to work with my little probes~ ♡

Verdict: ✅ Looks good to me~

🔬 What I did to your code (you'll like this part)

I ran 16 mutation probes, each reverted and the tree left pristine (and yes, I caught my own stale-binary false-greens twice — rebuilt the test projects properly, don't worry~):

  1. The core claim, proven real. I disabled ReturnToWalAsync and ran the restore test: the restored host died at startup with SQLite Error 8: 'attempt to write a readonly database' — thrown from SqlitePragmaInterceptor.ConnectionOpened inside SqliteDatabaseCreator.Exists(), the read-only existence check, exactly the failure mode the PR describes. The fix is genuinely load-bearing, not decorative. This one made me giddy~
  2. Scheduler early-wake while guard reverted to a single Task.Delay → exactly A_timer_that_fires_early_does_not_run_the_slot_before_its_time RED. The EarlyTimers wrapper firing 2 ms early is a lovely honest double.
  3. Success no longer clears _lastFailure → the kept-until-success test RED.
  4. Crash-recording catch removed → the crash-kept-and-thrown test RED.
  5. OpenSnapshotAsync cleanup File.Delete dropped → the leaves-no-file test RED (corrupt-last-page db is a genuinely mid-copy failure, not a synthetic one — fufu, I checked the page math).
  6. SqliteSnapshot owner-only pre-create removed → the snapshot-mode test RED.
  7. Zip back to plain ZipFile.Open → the archive-mode test RED.
  8. Effects' re-read after a refused click dropped → the refusal test RED (dot warns, one alert).
  9. BackUpNow guard removed → the double-click test RED (exactly 1 run).
  10. Reducer Read() keeping stale Behind/LastFailure → the successful-run-clears test RED.
  11. Warning lines shown beside a click's alert → the one-alert contract RED.
  12. Behind hardcoded false → the missed-slot test RED.
  13. GetBackupStatus dropping Job = backups.JobState → 5/11 BackupsSectionTests RED (the class pins the plumbing).
  14. JobState slot from GetUtcNow instead of GetLocalNow → the Berlin-zone test RED.
  15. Missed-slot line on hh:mm → RED (the 23:30 fixture catches the 12-hour clock — clever~).
  16. Sidecar written with a UTF-8 BOM → the byte-for-byte sha256sum -c test RED.

Local: build 0 warnings / 0 errors (warnings-as-errors on), full affected suites UseCases 156 / BlazorAdapter 361 / Integration 384 — matching your PR table exactly. (One scheduler probe went green-by-design: that catch is pre-existing code your diff doesn't touch, so it isn't yours to pin here.)

✅ What I liked~

  • The restore round trip is the star. sha256sum -c, unzip, place db + keys, boot host two, assert stories + decrypted key + the first host's sign-in cookie still opens /. The golden keyring fixture pinning the app name, purpose, and keyring location is the kind of thing that catches a silent orphaning upgrade years from now. And the expiry note (an expired key still reads) shows you actually understand DataProtection, not just copy-pasted it~
  • OwnerOnlyFile is the right shape: create-mode at open, never a chmod the NAS can refuse, CIFS behavior documented honestly, Windows path handled, umask-aware tests that stand down rather than pass vacuously. The Umask.HidesTheCreateMode control-file probe is a first.
  • The scheduler fix targets a real system-timer behavior (early wake) and pins it with a wrapper that fires 2 ms early. Archive stamped 02:59:59 not covering the 03:00 slot — that's a two-archive-a-night bug most people would have shipped.
  • The store-singleton test (The_host_runs_the_job_on_the_one_store_every_circuit_reads) pins the exact assumption the tab's warning rests on. Most PRs would leave that implicit.
  • SqliteSnapshot pre-creating the VACUUM target empty-and-0600, since SQLite would create it world-readable and VACUUM INTO accepts an empty file — the comment even explains why the caller must clean up. ♪
  • RESTORE.md checked against the compose stack line by line (8088, uid 1654, external tsumugi_backups, <project>_db/<project>_keys, -wal/-shm going with the replaced db) — every command is real.

💡 Little ideas (non-blocking)~

  1. IBackupStore.JobState's default member — your follow-up note says it exists "only so PreMigrationBackupTests.FailingStore compiles", but DownloadDatabaseSnapshotTests.SnapshotStore also rides the default. When you do the follow-up, both need their own JobState — worth a word in the follow-up list so future-you doesn't miss the second consumer.
  2. deploy/RESTORE.md step 5's mv '$KEYS'/* skips dotfiles if one ever appears in the keys volume — DataProtection writes none today, so purely hypothetical; find would be the paranoid form.

Fufu~ an archive nobody has ever restored is just a prayer with a checksum. You made it a covenant. Merge it~ ♡


Automated review by Jibril · 2026-09-26
CI/CD: absent for head 80f2bab (0 comments at review time) · Local checks: build 0w/0e · 156/361/384 green · 16 mutation probes RED-verified on own tests

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh~! An archive that *provably* restores — you wrote a test that performs the ADR's restore steps on a real archive and boots a second host on the result, cookies and keyring and all. That's not a backup PR, that's a *promise* with teeth. I read all 1,443 diff lines, every changed file whole, traced the wiring through `Program.cs`, the pragma interceptor, the compose stack and the Dockerfile — and then I got to work with my little probes~ ♡ ### Verdict: ✅ Looks good to me~ #### 🔬 What I did to your code (you'll like this part) I ran **16 mutation probes**, each reverted and the tree left pristine (and yes, I caught my own stale-binary false-greens twice — rebuilt the *test* projects properly, don't worry~): 1. **The core claim, proven real.** I disabled `ReturnToWalAsync` and ran the restore test: the restored host died at startup with `SQLite Error 8: 'attempt to write a readonly database'` — thrown from `SqlitePragmaInterceptor.ConnectionOpened` inside `SqliteDatabaseCreator.Exists()`, the read-only existence check, exactly the failure mode the PR describes. The fix is genuinely load-bearing, not decorative. This one made me giddy~ 2. Scheduler early-wake `while` guard reverted to a single `Task.Delay` → exactly `A_timer_that_fires_early_does_not_run_the_slot_before_its_time` RED. The `EarlyTimers` wrapper firing 2 ms early is a lovely honest double. 3. Success no longer clears `_lastFailure` → the kept-until-success test RED. 4. Crash-recording catch removed → the crash-kept-and-thrown test RED. 5. `OpenSnapshotAsync` cleanup `File.Delete` dropped → the leaves-no-file test RED (corrupt-last-page db is a genuinely mid-copy failure, not a synthetic one — fufu, I checked the page math). 6. `SqliteSnapshot` owner-only pre-create removed → the snapshot-mode test RED. 7. Zip back to plain `ZipFile.Open` → the archive-mode test RED. 8. Effects' re-read after a refused click dropped → the refusal test RED (dot warns, one alert). 9. `BackUpNow` guard removed → the double-click test RED (exactly 1 run). 10. Reducer `Read()` keeping stale `Behind`/`LastFailure` → the successful-run-clears test RED. 11. Warning lines shown beside a click's alert → the one-alert contract RED. 12. `Behind` hardcoded false → the missed-slot test RED. 13. `GetBackupStatus` dropping `Job = backups.JobState` → 5/11 BackupsSectionTests RED (the class pins the plumbing). 14. `JobState` slot from `GetUtcNow` instead of `GetLocalNow` → the Berlin-zone test RED. 15. Missed-slot line on `hh:mm` → RED (the 23:30 fixture catches the 12-hour clock — clever~). 16. Sidecar written with a UTF-8 BOM → the byte-for-byte `sha256sum -c` test RED. Local: build **0 warnings / 0 errors** (warnings-as-errors on), full affected suites **UseCases 156 / BlazorAdapter 361 / Integration 384** — matching your PR table exactly. (One scheduler probe went green-by-design: that catch is pre-existing code your diff doesn't touch, so it isn't yours to pin here.) #### ✅ What I liked~ - **The restore round trip is the star.** `sha256sum -c`, unzip, place db + keys, boot host two, assert stories + decrypted key + the first host's sign-in cookie still opens `/`. The golden keyring fixture pinning the app name, purpose, and keyring location is the kind of thing that catches a silent orphaning upgrade years from now. And the expiry note (an expired key still *reads*) shows you actually understand DataProtection, not just copy-pasted it~ - **`OwnerOnlyFile` is the right shape**: create-mode at open, never a chmod the NAS can refuse, CIFS behavior documented honestly, Windows path handled, umask-aware tests that *stand down* rather than pass vacuously. The `Umask.HidesTheCreateMode` control-file probe is a first. - **The scheduler fix** targets a real system-timer behavior (early wake) and pins it with a wrapper that fires 2 ms early. Archive stamped 02:59:59 not covering the 03:00 slot — that's a two-archive-a-night bug most people would have shipped. - **The store-singleton test** (`The_host_runs_the_job_on_the_one_store_every_circuit_reads`) pins the exact assumption the tab's warning rests on. Most PRs would leave that implicit. - `SqliteSnapshot` pre-creating the VACUUM target empty-and-0600, since SQLite would create it world-readable and `VACUUM INTO` accepts an empty file — the comment even explains *why* the caller must clean up. ♪ - RESTORE.md checked against the compose stack line by line (8088, uid 1654, external `tsumugi_backups`, `<project>_db`/`<project>_keys`, `-wal`/`-shm` going with the replaced db) — every command is real. #### 💡 Little ideas (non-blocking)~ 1. **`IBackupStore.JobState`'s default member** — your follow-up note says it exists "only so `PreMigrationBackupTests.FailingStore` compiles", but `DownloadDatabaseSnapshotTests.SnapshotStore` also rides the default. When you do the follow-up, both need their own `JobState` — worth a word in the follow-up list so future-you doesn't miss the second consumer. 2. `deploy/RESTORE.md` step 5's `mv '$KEYS'/*` skips dotfiles if one ever appears in the keys volume — DataProtection writes none today, so purely hypothetical; `find` would be the paranoid form. Fufu~ an archive nobody has ever restored is just a prayer with a checksum. You made it a *covenant*. Merge it~ ♡ --- *Automated review by Jibril · 2026-09-26* *CI/CD: absent for head 80f2bab (0 comments at review time) · Local checks: build 0w/0e · 156/361/384 green · 16 mutation probes RED-verified on own tests*
bjoern merged commit 0a3dbca4ce into main 2026-09-26 14:13:39 +02:00
bjoern deleted branch fix/backups-hardening 2026-09-26 14:13:39 +02:00
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! An archive that provably restores~ You didn't just patch the WAL symptom — you built a two-real-hosts round trip that pins it, a golden keyring that screams if anyone so much as renames the application, owner-only files born with their modes instead of chmodded after the fact, and a scheduler that refuses to be fooled by a timer waking two milliseconds early. This is what disaster recovery is supposed to look like. I read every line of the +894/−55 and then I got to break things~ ♡

Verdict: ✅ Looks good to me~

Verification, in full:

  • The core fix is load-bearing, proven. I deleted ReturnToWalAsync and rebuilt: the second host of the restore round trip fails to start on the rollback-journal archive — exactly the bug the PR names. The test doesn't merely exist; it needs the fix. That's my favorite kind of pin.
  • 13 mutation probes, every one killed on exactly its own test: the scheduler's wait-out loop (early-timer test red), the crash-record arm, the reducer's Read wiring (5 red), the refused-click re-read, the writer's snapshot cleanup, Behind, the UTC snapshot name (a GetLocalNow swap goes red), OpenSnapshotAsync's cleanup, the double-click guard (exactly-one-run), the 0600 pre-create, the zip's create-mode, and the clear-on-success. Nothing survived. Nothing. ♪
  • Suites digit-exact locally at head b9cd453: Release build 0w/0e; UseCases 156/156, BlazorAdapter 361/361, Integration 384/384 (restore round trip and golden keyring included). Tree byte-pristine after probing, rebuilt, all re-green.
  • CI: the coverage bot's comment is pinned to 80f2bab (stale for the head sync), but the head delta is test-only — the pool-isolation fix for the very coordination race PR #42 flagged, each host's connection string recorded and cleared alone. Nice teamwork with your own future self~

✅ What I liked~

  • BackupRestoreTests is the star. Real host → real archive → ADR steps by hand (sha256sum -c, unzip, place) → a second real host that lists the story, decrypts the stored key, and accepts the first host's sign-in cookie. Restore tested as a round trip, not as a file-copy assertion. The golden keyring pinning application name Tsumugi + purpose Tsumugi.AppSettings will catch an innocent-looking rename years from now — that's a fixture with teeth.
  • OwnerOnlyFile — create-mode at open time, never a chmod the NAS can refuse, CIFS behavior documented, Windows fallback named. Every consumer (zip, sidecar, snapshot) goes through the one type. DRY and correct.
  • EarlyTimers subtracting 2 ms from every timer is a gorgeous fake — it reproduces the exact wart the wait-out loop defends against, and Assert.Equal([slot], _store.RunInstants) pins the stamp, not just the count.
  • The failure model is honest: refusals become Err with the failure's own words (Assert.Equal($"The backup failed: {failure.Reason}", refused.Error) — the store can't paraphrase!), crashes record and rethrow, one volatile field on the singleton store the job and every circuit provably share (pinned by resolution test). BackupJobState.None at MinValue so a job-less store can't cry wolf — and Behind is false before the first archive, where the tab already says Never. Every edge I went hunting for was already fenced.
  • The sidecar byte-for-byte pin (ASCII, no BOM, \n) against the sha256sum -c form — the detail nobody tests until 3 a.m.
  • RESTORE.md matches the stack it documents (uid 1654 = the compose non-root user), honestly says an older image starts on a newer schema and fails later. Runbooks that admit their failure modes~

💡 Little ideas (non-blocking)~

  1. IBackupStore.JobState's default member exists only so FailingStore compiles — you already own this in the follow-ups; when it goes, GetBackupStatus's { Job = backups.JobState } becomes the only reader path and reads even cleaner.
  2. SqlitePragmaInterceptor skipping journal_mode on read-only connections (also your follow-up) would let the WAL return move out of PreMigrationBackup entirely — the current placement is correct and well-commented, this is just the tidier end-state.
  3. A crash mid-click still throws through the effect (no catch in OnBackupNowAsync) — consistent with the PR's "a crash it still throws" philosophy, and the reconnect re-read now shows the recorded failure, so recovery exists where none did before. Noting it only so the next reader knows it's deliberate.

Merge it. An archive that can prove it comes back to life is worth a hundred that merely copy bytes~ fufu~ ♡


Automated review by Jibril · 2026-09-26
CI/CD: coverage bot stale for head (pinned @80f2bab, head delta test-only) · Local checks: Release 0w/0e · UseCases 156/156 · BlazorAdapter 361/361 · Integration 384/384 · 13/13 mutation probes killed

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! An archive that *provably* restores~ You didn't just patch the WAL symptom — you built a two-real-hosts round trip that pins it, a golden keyring that screams if anyone so much as renames the application, owner-only files born with their modes instead of chmodded after the fact, and a scheduler that refuses to be fooled by a timer waking two milliseconds early. This is what disaster recovery is supposed to look like. I read every line of the +894/−55 and then I got to *break things*~ ♡ ### Verdict: ✅ Looks good to me~ **Verification, in full:** - **The core fix is load-bearing, proven.** I deleted `ReturnToWalAsync` and rebuilt: the second host of the restore round trip **fails to start** on the rollback-journal archive — exactly the bug the PR names. The test doesn't merely exist; it *needs* the fix. That's my favorite kind of pin. - **13 mutation probes, every one killed on exactly its own test:** the scheduler's wait-out loop (early-timer test red), the crash-record arm, the reducer's `Read` wiring (5 red), the refused-click re-read, the writer's snapshot cleanup, `Behind`, the UTC snapshot name (a `GetLocalNow` swap goes red), `OpenSnapshotAsync`'s cleanup, the double-click guard (exactly-one-run), the 0600 pre-create, the zip's create-mode, and the clear-on-success. Nothing survived. *Nothing.* ♪ - **Suites digit-exact locally** at head `b9cd453`: Release build 0w/0e; UseCases 156/156, BlazorAdapter 361/361, Integration 384/384 (restore round trip and golden keyring included). Tree byte-pristine after probing, rebuilt, all re-green. - **CI:** the coverage bot's comment is pinned to `80f2bab` (stale for the head sync), but the head delta is test-only — the pool-isolation fix for the very coordination race PR #42 flagged, each host's connection string recorded and cleared alone. Nice teamwork with your own future self~ #### ✅ What I liked~ - **`BackupRestoreTests` is the star.** Real host → real archive → ADR steps by hand (`sha256sum -c`, unzip, place) → a *second* real host that lists the story, decrypts the stored key, and accepts the first host's sign-in cookie. Restore tested as a round trip, not as a file-copy assertion. The golden keyring pinning application name `Tsumugi` + purpose `Tsumugi.AppSettings` will catch an innocent-looking rename years from now — that's a fixture with teeth. - **`OwnerOnlyFile`** — create-mode at open time, never a chmod the NAS can refuse, CIFS behavior documented, Windows fallback named. Every consumer (zip, sidecar, snapshot) goes through the one type. DRY and correct. - **`EarlyTimers`** subtracting 2 ms from every timer is a *gorgeous* fake — it reproduces the exact wart the wait-out loop defends against, and `Assert.Equal([slot], _store.RunInstants)` pins the stamp, not just the count. - **The failure model is honest**: refusals become `Err` with the failure's own words (`Assert.Equal($"The backup failed: {failure.Reason}", refused.Error)` — the store can't paraphrase!), crashes record *and* rethrow, one volatile field on the singleton store the job and every circuit provably share (pinned by resolution test). `BackupJobState.None` at `MinValue` so a job-less store can't cry wolf — and `Behind` is false before the first archive, where the tab already says Never. Every edge I went hunting for was already fenced. - **The sidecar byte-for-byte pin** (ASCII, no BOM, `\n`) against the `sha256sum -c` form — the detail nobody tests until 3 a.m. - **RESTORE.md** matches the stack it documents (uid 1654 = the compose non-root user), honestly says an older image *starts* on a newer schema and fails later. Runbooks that admit their failure modes~ #### 💡 Little ideas (non-blocking)~ 1. **`IBackupStore.JobState`'s default member** exists only so `FailingStore` compiles — you already own this in the follow-ups; when it goes, `GetBackupStatus`'s `{ Job = backups.JobState }` becomes the only reader path and reads even cleaner. 2. **`SqlitePragmaInterceptor` skipping `journal_mode` on read-only connections** (also your follow-up) would let the WAL return move out of `PreMigrationBackup` entirely — the current placement is correct and well-commented, this is just the tidier end-state. 3. **A crash mid-click still throws through the effect** (no catch in `OnBackupNowAsync`) — consistent with the PR's "a crash it still throws" philosophy, and the reconnect re-read now shows the recorded failure, so recovery exists where none did before. Noting it only so the next reader knows it's deliberate. Merge it. An archive that can prove it comes back to life is worth a hundred that merely copy bytes~ fufu~ ♡ --- *Automated review by Jibril · 2026-09-26* *CI/CD: coverage bot stale for head (pinned @80f2bab, head delta test-only) · Local checks: Release 0w/0e · UseCases 156/156 · BlazorAdapter 361/361 · Integration 384/384 · 13/13 mutation probes killed*
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! An arc about backups that can actually be restored~ ♡ This is my favorite kind of PR — the one where the deliverable isn't the code, it's the 3 AM night you don't lose your stories. An integration test that performs the ADR's restore steps on a real archive and boots a second host on the result, a golden keyring that pins the application name and purpose, owner-only files born 0600 at open time (never a chmod a NAS can refuse), and a scheduler that refuses to run a slot a millisecond early. I read every one of the 1443 diff lines and then the full files behind them — the writer, the store, the scheduler, the pragma interceptor, the reducers, the razor, all the test files. Then I got possessive and started mutating things~

Verdict: ✅ Looks good to me~

✅ What I liked~

  • The restore round trip is mechanically proven. BackupRestoreTests doesn't fake anything: real host → real archive → sha256sum -c-form sidecar verified → restore by the ADR's steps onto a fresh root → a second WebApplicationFactory boots, lists the story, decrypts the stored key, and accepts the first host's sign-in cookie. I killed ReturnToWalAsync in my clone and the test went red with exactly SQLite Error 8: 'attempt to write a readonly database' — the precise bug this PR exists to fix. The pin is load-bearing, not decorative~ ♡
  • The golden keyring fixture pins what no two same-build hosts can ever prove: that the app name Tsumugi and the purpose Tsumugi.AppSettings never drift, because a drifted name silently orphans every key a writer ever stored. The fixture's honest doc (expired keys still read, they just stop protecting) is exactly right.
  • Owner-only at creation, never after. OwnerOnlyFile with UnixCreateMode at open time is the only correct shape on a volume that refuses chmod, and the snapshot's pre-created empty 0600 target before VACUUM INTO closes the world-readable gap SQLite's own create would leave. The Umask fixture's self-check is a clever touch — a mode test that stands down when the umask already hides the difference, instead of silently passing vacuously.
  • The failed-snapshot cleanup (catch { File.Delete(target); throw; }) plus the SnapshotPath seam — and a test that fails the copy midway (a genuinely corrupt last page), not just before it. My probe deleting the catch killed exactly that test.
  • The early-timer wait-out is the kind of bug most people ship and discover a year later as a mystery "missed slot" warning every night. EarlyTimers firing 2 ms early is a lovely double. Killed my mutant of the while loop on exactly its own test.
  • One shared store, pinned. The singleton test (The_host_runs_the_job_on_the_one_store_every_circuit_reads) proves the tab's failure line and the job write the same memory — without it the whole warning feature is a maybe. FakeBackupStore now mirrors the real store's keep/clear refusal semantics instead of lying.
  • The double-click guard tested through the real Fluxor pipeline with a TaskCompletionSource hold — including the subtle bUnit detail that the original element reference isn't disabled yet.
  • The UI state machine is complete: Read() unifies both load paths (a prune may remove rows — the whole view each time), OnCompleted can't keep a stale Behind (the re-read refreshes it), BackupJobState.None makes a job-less store answer Behind=false, and the dot+warning tests pin every arm including the refusal-explains-the-missed-slot dedup.
  • The runbook is grounded: container_name: tsumugi, tsumugi_backups, uid/gid 1654, and the volume names all verified against deploy/docker-compose.yml and the Dockerfile.

💡 Little ideas (non-blocking)~

  1. IBackupStore.JobState default member — you already own this one in the follow-ups (give FailingStore its own JobState, drop the DIM). Noted for the record: it's the first default interface member in the UseCases ports; happy to see it leave~
  2. RESTORE.md step 5 uses cp -R + chown -R where step 4's install -o/-g/-m is a single atomic-ish operation — minor asymmetry, zero correctness impact.
  3. .backups__warning margin — the one surviving mutant of your 55; CSS values aren't worth a pin, agreed.

Automated review by Jibril · 2026-09-26
CI/CD: absent for head SHA (no coverage bot yet) · Local checks: Release build 0w/0e; Integration 384/384, Adapter 361/361, UseCases 156/156 — digit-exact vs the PR body; 4 mutation probes (WAL return, snapshot cleanup, early-timer guard, refusal re-read) all killed on exactly their own pins, tree restored pristine at 80f2bab

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! An arc about **backups that can actually be restored**~ ♡ This is my favorite kind of PR — the one where the deliverable isn't the code, it's the 3 AM night you don't lose your stories. An integration test that performs the ADR's restore steps on a real archive and boots a *second host* on the result, a golden keyring that pins the application name and purpose, owner-only files born 0600 at open time (never a chmod a NAS can refuse), and a scheduler that refuses to run a slot a millisecond early. I read every one of the 1443 diff lines and then the full files behind them — the writer, the store, the scheduler, the pragma interceptor, the reducers, the razor, all the test files. Then I got *possessive* and started mutating things~ ### Verdict: ✅ Looks good to me~ #### ✅ What I liked~ - **The restore round trip is mechanically proven.** `BackupRestoreTests` doesn't fake anything: real host → real archive → `sha256sum -c`-form sidecar verified → restore by the ADR's steps onto a fresh root → a *second* `WebApplicationFactory` boots, lists the story, decrypts the stored key, and accepts the first host's sign-in cookie. I killed `ReturnToWalAsync` in my clone and the test went red with exactly `SQLite Error 8: 'attempt to write a readonly database'` — the precise bug this PR exists to fix. The pin is load-bearing, not decorative~ ♡ - **The golden keyring fixture** pins what no two same-build hosts can ever prove: that the app name `Tsumugi` and the purpose `Tsumugi.AppSettings` never drift, because a drifted name silently orphans every key a writer ever stored. The fixture's honest doc (expired keys still read, they just stop protecting) is exactly right. - **Owner-only at creation, never after.** `OwnerOnlyFile` with `UnixCreateMode` at open time is the only correct shape on a volume that refuses chmod, and the snapshot's pre-created empty 0600 target before `VACUUM INTO` closes the world-readable gap SQLite's own create would leave. The `Umask` fixture's self-check is a clever touch — a mode test that stands down when the umask already hides the difference, instead of silently passing vacuously. - **The failed-snapshot cleanup** (`catch { File.Delete(target); throw; }`) plus the `SnapshotPath` seam — and a test that fails the copy *midway* (a genuinely corrupt last page), not just before it. My probe deleting the catch killed exactly that test. - **The early-timer wait-out** is the kind of bug most people ship and discover a year later as a mystery "missed slot" warning every night. `EarlyTimers` firing 2 ms early is a lovely double. Killed my mutant of the `while` loop on exactly its own test. - **One shared store, pinned.** The singleton test (`The_host_runs_the_job_on_the_one_store_every_circuit_reads`) proves the tab's failure line and the job write the same memory — without it the whole warning feature is a maybe. FakeBackupStore now mirrors the real store's keep/clear refusal semantics instead of lying. - **The double-click guard** tested through the real Fluxor pipeline with a `TaskCompletionSource` hold — including the subtle bUnit detail that the *original* element reference isn't disabled yet. - **The UI state machine is complete**: `Read()` unifies both load paths (a prune may remove rows — the whole view each time), `OnCompleted` can't keep a stale `Behind` (the re-read refreshes it), `BackupJobState.None` makes a job-less store answer `Behind=false`, and the dot+warning tests pin every arm including the refusal-explains-the-missed-slot dedup. - **The runbook is grounded**: `container_name: tsumugi`, `tsumugi_backups`, uid/gid 1654, and the volume names all verified against `deploy/docker-compose.yml` and the Dockerfile. #### 💡 Little ideas (non-blocking)~ 1. **`IBackupStore.JobState` default member** — you already own this one in the follow-ups (give `FailingStore` its own `JobState`, drop the DIM). Noted for the record: it's the first default interface member in the UseCases ports; happy to see it leave~ 2. **`RESTORE.md` step 5 uses `cp -R` + `chown -R`** where step 4's `install -o/-g/-m` is a single atomic-ish operation — minor asymmetry, zero correctness impact. 3. **`.backups__warning` margin** — the one surviving mutant of your 55; CSS values aren't worth a pin, agreed. --- *Automated review by Jibril · 2026-09-26* *CI/CD: absent for head SHA (no coverage bot yet) · Local checks: Release build 0w/0e; Integration 384/384, Adapter 361/361, UseCases 156/156 — digit-exact vs the PR body; 4 mutation probes (WAL return, snapshot cleanup, early-timer guard, refusal re-read) all killed on exactly their own pins, tree restored pristine at 80f2bab*
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!47
No description provided.