feat: Phase 1e-c — Backups #18

Merged
bjoern merged 1 commit from feat/p1e-backups into main 2026-10-18 16:30:48 +02:00
Member

Phase 1e, part c: rotating backups, the pre-migration backup, the Backups tab and the snapshot download (ADR 0012; stories/settings.md). This is a port of Tsumugi's backup code, renamed and scoped to ADR 0012's own decisions. 1e-d (Appearance, which ticks 1e ✅) comes next.

What's in

Infrastructure (Backups/, ported)

  • BackupWriter:
    • writes shikigami-backup-YYYYMMDD-HHmmss.zip (UTC) holding shikigami.db (a VACUUM INTO copy taken into the temp directory), the keyring as keys/…, and manifest.txt last (timestamp, appVersion, userVersion, migration, files);
    • writes it as .zip.tmp, then moves it into place, then adds a .sha256 sidecar;
    • creates both files owner-only (0600) through the create mode;
    • prunes by the UTC stamp in the name, and only after a successful write;
    • runs one cycle at a time.
  • BackupScheduler:
    • runs at SHIKIGAMI_BACKUP_SCHEDULE, 03:30 local by default, after the default reflection hour (ADR 0012);
    • runs a catch-up five minutes after start when no archive covers the last slot;
    • never runs before its slot;
    • can't take the host down.
  • FileSystemBackupStore:
    • turns I/O and SQLite failures into refusals, and lets a bug through;
    • keeps the last failure in memory until a cycle succeeds;
    • gives a database-only snapshot that is deleted when its stream closes.
  • Pre-migration backup, inside DatabaseMigrator.StartingAsync (after the volume probe, before any hosted service):
    • a restored database is put back into write-ahead logging;
    • an existing database with migrations pending is backed up first, and a failed backup stops the start, naming the fix;
    • a fingerprint marker means a migration that keeps failing in a restarting container takes one archive per state of the database, not one per start.
  • AddShikigamiInfrastructure(…, backups) registers all of this only when the host passes options. The integration tests' base composition has none.

Server

  • BackupConfiguration reads SHIKIGAMI_BACKUP_SCHEDULE (HH:mm) and SHIKIGAMI_BACKUP_RETENTION_DAYS strictly:
    • a malformed value fails startup by name;
    • a blank value takes the default;
    • retention is bounded at 1–36500 days, because a larger value would make every prune throw after its archive was written.
  • BlobSweepOptions now comes from the same strict retention, where before it was read leniently. Blobs are kept a week past it (ADR 0005).
  • GET /backups/snapshot: gated like every route, the database alone, no-store.

Adapter

  • Backups tab:
    • last backup with a status dot, schedule, and retention (its explanation behind an info tip);
    • the activity log's retention: entries 180 days and run detail 30 days, read-only, as the brief has it; the settings arrive with those tables (2c, 2g);
    • the kept archives;
    • Back up now with its click guard, and Download snapshot;
    • warnings for a failed last cycle or a missed slot.
  • A read that throws now ends on "The backups could not be read." with Try again. Tsumugi's tab would sit on "Loading…" for good.

"Content unavailable" (ADR 0012)

  • FileView.ContentUnavailable is set when a binary file's blob can't be read: missing, or refused (an IOException or UnauthorizedAccessException from a dropped NAS mount or a wrong owner).
  • The file pane then shows the alert in place of the preview or content, and hides Download, including History's download of the current version. It no longer fails to open.

Docs

  • deploy/RESTORE.md is the runbook for Shikigami's stack. It notes that files' binary contents and browser profiles are not in the archive, and that a restore leaves those volumes as they are.

Tests

1980 → 2065, green in local time and under TZ=UTC. The 85 new tests:

  • Integration (60):
    • BackupWriterTests (13): what an archive holds, a consistent snapshot under a concurrent writer, owner-only files, the sidecar, and pruning by the name stamp. The prune boundary is pinned: one second past the retention goes, exactly at it stays.
    • BackupSchedulerTests (7) and BackupScheduleTests (4): a fake clock with DST, the catch-up grace, an early timer, a dead directory at boot.
    • FileSystemBackupStoreTests (9).
    • PreMigrationBackupTests (5):
      • an upgrade from FileCreatedBy with a familiar in it is backed up, and a current database isn't;
      • no backups, or a refused or throwing cycle, stops the start with the fix;
      • one archive per state of the database: a pre-made AppSettings table makes the upgrade fail on every start;
      • the fingerprint, against Familiars;
      • through the real host: a start on an older database takes the archive before it migrates.
    • BackupRestoreTests (2): the RESTORE.md steps on a real archive, then a second host on the result. The familiar, the stored key and the first host's sign-in all come back. With the blob volume kept, the image's bytes read back; without it, the file shows content unavailable.
    • BackupConfigurationTests (17):
      • the 03:30 default;
      • blank values;
      • malformed schedules, and retention outside 1–36500, including " 14" and "+14";
      • a bad value crashing startup by name;
      • through the host, retention 30 → sweep grace 37 days.
    • BackupSnapshotEndpointTests (3).
  • Adapter (21):
    • BackupsSectionTests (15): the tab's states; the facts and their info tips; a read that throws, then Try again; Tsumugi's two bUnit failure paths (the button stops spinning after the containment modal and runs again).
    • BackupsSettingsTests (6): reducers, effects, and each throw path settling.
  • Use cases (1): DownloadDatabaseSnapshot.
  • File pane (3): content unavailable for an image and a binary file (no Download, and none in History), and for a blob the volume refuses to read.

Review and probes

  • Review: a read-only review workflow raised 12 findings, and 8 were confirmed. All 8 are applied:
    • a blob volume that can't be read no longer fails the pane;
    • retention is bounded;
    • History hides the current version's download;
    • Tsumugi's word "journal" in a comment is reworded;
    • Tsumugi's four backup failure-path tests are ported;
    • the host test for retention feeding the sweep is added;
    • blank and malformed values are pinned;
    • the activity log's info tip is checked.
  • Of the rejected four, I took two anyway: the RESTORE.md browser-profile wording and a 03:00 comment. The other two were a duplicate and a matter of taste.
  • Mutation probes: round 1 ran 34; 31 were killed. The other three:
    • a missing prune boundary: a test was added, and the mutant is killed in round 2;
    • an equivalent mutant over a dead LoadError = null: the line is removed;
    • one probe that didn't compile: it was rewritten to start the migrator without the backup store, and the new host test kills it in round 2.
  • I rebuilt after the last mutant before the counts above.

Browser-verified

Against the seeded world (SHIKIGAMI_SEED_DEV_DATA=true), headless Chrome at 1280 px and 390 px:

  • The scheduler logged "No archive covers the last 03:30 slot; a backup runs in 00:05:00."
  • The Backups tab showed "Never", "Daily at 03:30, server time", "7 days" and the activity log's retention.
  • Back up now made shikigami-backup-…zip (17 KB) holding shikigami.db, keys/key-….xml and manifest.txt, with a .sha256 beside it, both mode 0600. The tab then showed "just now" with a green dot.
  • Download snapshot saved shikigami-YYYYMMDD-HHmmss.db, starting with the SQLite header.
  • No horizontal scroll, and no console errors or warnings.

🤖 Generated with Claude Code

Phase 1e, part c: **rotating backups**, the pre-migration backup, the **Backups** tab and the snapshot download (ADR 0012; stories/settings.md). This is a port of Tsumugi's backup code, renamed and scoped to ADR 0012's own decisions. 1e-d (Appearance, which ticks 1e ✅) comes next. ## What's in **Infrastructure (`Backups/`, ported)** - `BackupWriter`: - writes `shikigami-backup-YYYYMMDD-HHmmss.zip` (UTC) holding `shikigami.db` (a `VACUUM INTO` copy taken into the temp directory), the keyring as `keys/…`, and `manifest.txt` last (timestamp, appVersion, userVersion, migration, files); - writes it as `.zip.tmp`, then moves it into place, then adds a `.sha256` sidecar; - creates both files owner-only (0600) through the create mode; - prunes by the UTC stamp in the name, and only after a successful write; - runs one cycle at a time. - `BackupScheduler`: - runs at `SHIKIGAMI_BACKUP_SCHEDULE`, **03:30** local by default, after the default reflection hour (ADR 0012); - runs a catch-up five minutes after start when no archive covers the last slot; - never runs before its slot; - can't take the host down. - `FileSystemBackupStore`: - turns I/O and SQLite failures into refusals, and lets a bug through; - keeps the last failure in memory until a cycle succeeds; - gives a database-only snapshot that is deleted when its stream closes. - **Pre-migration backup, inside `DatabaseMigrator.StartingAsync`** (after the volume probe, before any hosted service): - a restored database is put back into write-ahead logging; - an existing database with migrations pending is backed up first, and a failed backup stops the start, naming the fix; - a fingerprint marker means a migration that keeps failing in a restarting container takes one archive per state of the database, not one per start. - `AddShikigamiInfrastructure(…, backups)` registers all of this only when the host passes options. The integration tests' base composition has none. **Server** - `BackupConfiguration` reads `SHIKIGAMI_BACKUP_SCHEDULE` (`HH:mm`) and `SHIKIGAMI_BACKUP_RETENTION_DAYS` strictly: - a malformed value fails startup by name; - a blank value takes the default; - retention is bounded at **1–36500 days**, because a larger value would make every prune throw after its archive was written. - `BlobSweepOptions` now comes from the same strict retention, where before it was read leniently. Blobs are kept a week past it (ADR 0005). - `GET /backups/snapshot`: gated like every route, the database alone, `no-store`. **Adapter** - **Backups tab:** - last backup with a status dot, schedule, and retention (its explanation behind an info tip); - **the activity log's retention**: entries 180 days and run detail 30 days, read-only, as the brief has it; the settings arrive with those tables (2c, 2g); - the kept archives; - **Back up now** with its click guard, and **Download snapshot**; - warnings for a failed last cycle or a missed slot. - A read that throws now ends on "The backups could not be read." with **Try again**. Tsumugi's tab would sit on "Loading…" for good. **"Content unavailable" (ADR 0012)** - `FileView.ContentUnavailable` is set when a binary file's blob can't be read: missing, or refused (an `IOException` or `UnauthorizedAccessException` from a dropped NAS mount or a wrong owner). - The file pane then shows the alert in place of the preview or content, and hides Download, including History's download of the current version. It no longer fails to open. **Docs** - `deploy/RESTORE.md` is the runbook for Shikigami's stack. It notes that files' binary contents and browser profiles are not in the archive, and that a restore leaves those volumes as they are. ## Tests 1980 → **2065**, green in local time and under `TZ=UTC`. The 85 new tests: - **Integration (60):** - **`BackupWriterTests` (13):** what an archive holds, a consistent snapshot under a concurrent writer, owner-only files, the sidecar, and pruning by the name stamp. The prune boundary is pinned: one second past the retention goes, exactly at it stays. - **`BackupSchedulerTests` (7) and `BackupScheduleTests` (4):** a fake clock with DST, the catch-up grace, an early timer, a dead directory at boot. - **`FileSystemBackupStoreTests` (9).** - **`PreMigrationBackupTests` (5):** - an upgrade from `FileCreatedBy` with a familiar in it is backed up, and a current database isn't; - no backups, or a refused or throwing cycle, stops the start with the fix; - one archive per state of the database: a pre-made `AppSettings` table makes the upgrade fail on every start; - the fingerprint, against `Familiars`; - **through the real host**: a start on an older database takes the archive before it migrates. - **`BackupRestoreTests` (2):** the RESTORE.md steps on a real archive, then a second host on the result. The familiar, the stored key and the first host's sign-in all come back. With the blob volume kept, the image's bytes read back; without it, the file shows content unavailable. - **`BackupConfigurationTests` (17):** - the 03:30 default; - blank values; - malformed schedules, and retention outside 1–36500, including `" 14"` and `"+14"`; - a bad value crashing startup by name; - through the host, retention 30 → sweep grace 37 days. - **`BackupSnapshotEndpointTests` (3).** - **Adapter (21):** - **`BackupsSectionTests` (15):** the tab's states; the facts and their info tips; a read that throws, then Try again; Tsumugi's two bUnit failure paths (the button stops spinning after the containment modal and runs again). - **`BackupsSettingsTests` (6):** reducers, effects, and each throw path settling. - **Use cases (1):** `DownloadDatabaseSnapshot`. - **File pane (3):** content unavailable for an image and a binary file (no Download, and none in History), and for a blob the volume refuses to read. ## Review and probes - **Review:** a read-only review workflow raised 12 findings, and 8 were confirmed. All 8 are applied: - a blob volume that can't be read no longer fails the pane; - retention is bounded; - History hides the current version's download; - Tsumugi's word "journal" in a comment is reworded; - Tsumugi's four backup failure-path tests are ported; - the host test for retention feeding the sweep is added; - blank and malformed values are pinned; - the activity log's info tip is checked. - Of the rejected four, I took two anyway: the RESTORE.md browser-profile wording and a 03:00 comment. The other two were a duplicate and a matter of taste. - **Mutation probes:** round 1 ran 34; 31 were killed. The other three: - **a missing prune boundary:** a test was added, and the mutant is killed in round 2; - **an equivalent mutant over a dead `LoadError = null`:** the line is removed; - **one probe that didn't compile:** it was rewritten to start the migrator without the backup store, and the new host test kills it in round 2. - I rebuilt after the last mutant before the counts above. ## Browser-verified Against the seeded world (`SHIKIGAMI_SEED_DEV_DATA=true`), headless Chrome at 1280 px and 390 px: - The scheduler logged "No archive covers the last 03:30 slot; a backup runs in 00:05:00." - The Backups tab showed "Never", "Daily at 03:30, server time", "7 days" and the activity log's retention. - **Back up now** made `shikigami-backup-…zip` (17 KB) holding `shikigami.db`, `keys/key-….xml` and `manifest.txt`, with a `.sha256` beside it, both mode 0600. The tab then showed "just now" with a green dot. - **Download snapshot** saved `shikigami-YYYYMMDD-HHmmss.db`, starting with the SQLite header. - No horizontal scroll, and no console errors or warnings. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat: Phase 1e-c — Backups
All checks were successful
CI / build (pull_request) Successful in 27s
CI / image (pull_request) Successful in 34s
CI / test (pull_request) Successful in 1m7s
41edb6e58e
Rotating backups (ADR 0012), ported from Tsumugi: a nightly VACUUM INTO archive at 03:30 local
with the keyring and a manifest, written as .zip.tmp and moved, a .sha256 sidecar, owner-only
files, pruning by the UTC stamp in the name after a successful write, and a five-minute catch-up
on start. A start that finds migrations pending on an existing database backs it up first and
refuses to migrate if that fails. The schedule and retention are read strictly, and the retention
feeds the blob sweep's grace. The Backups tab shows the last backup, schedule, retention, the
activity log's retention and the kept archives, with Back up now and Download snapshot
(GET /backups/snapshot, the database alone). A binary file whose bytes cannot be read from the
blob volume shows "Content unavailable" instead of failing the file browser. deploy/RESTORE.md
is the runbook, and an integration test performs it on a real archive.

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: 41edb6e · Generated: 2026-10-18 14:20:43 UTC · Revision: #1

Summary

Summary
Generated on: 10/18/2026 - 14:20:42
Coverage date: 10/18/2026 - 14:20:06 - 10/18/2026 - 14:20:39
Parser: MultiReport (5x Cobertura)
Assemblies: 6
Classes: 377
Files: 204
Line coverage: 95.9% (10371 of 10806)
Covered lines: 10371
Uncovered lines: 435
Coverable lines: 10806
Total lines: 21323
Branch coverage: 89.7% (5550 of 6185)
Covered branches: 5550
Total branches: 6185
Method coverage: Feature is only available for sponsors

Coverage

Shikigami.BlazorAdapter - 95.2%
Name Line Branch
Shikigami.BlazorAdapter 95.2% 88.1%
Shikigami.BlazorAdapter.BlazorAdapterAssembly 100%
Shikigami.BlazorAdapter.Cast.CastActionFailed 100%
Shikigami.BlazorAdapter.Cast.CastEffects 93.3% 93.7%
Shikigami.BlazorAdapter.Cast.CastLoaded 100%
Shikigami.BlazorAdapter.Cast.CastReducers 93.3% 50%
Shikigami.BlazorAdapter.Cast.CastState 100% 100%
Shikigami.BlazorAdapter.Cast.CreateFamiliarFailed 100%
Shikigami.BlazorAdapter.Cast.CreateFamiliarRequested 100%
Shikigami.BlazorAdapter.Cast.DuplicateFamiliarRequested 100%
Shikigami.BlazorAdapter.Cast.FamiliarAvatar 100% 75%
Shikigami.BlazorAdapter.Cast.FamiliarCreated 100%
Shikigami.BlazorAdapter.Cast.NewFamiliarDialog 97.2% 83.3%
Shikigami.BlazorAdapter.Cast.PurgeFamiliarRequested 100%
Shikigami.BlazorAdapter.Cast.SetFamiliarArchivedRequested 100%
Shikigami.BlazorAdapter.Cast.SetFamiliarTrashedRequested 100%
Shikigami.BlazorAdapter.Cast.TrashPage 89.4% 93.7%
Shikigami.BlazorAdapter.EffectCalls 100%
Shikigami.BlazorAdapter.Files.ConflictSettled 100%
Shikigami.BlazorAdapter.Files.DismissFileError 100%
Shikigami.BlazorAdapter.Files.EntryMissing 100%
Shikigami.BlazorAdapter.Files.EntryOpened 100%
Shikigami.BlazorAdapter.Files.FileActionDone 100%
Shikigami.BlazorAdapter.Files.FileActionFailed 100%
Shikigami.BlazorAdapter.Files.FileActionRequested 100%
Shikigami.BlazorAdapter.Files.FileLinks 100% 100%
Shikigami.BlazorAdapter.Files.FilePane 95% 88.3%
Shikigami.BlazorAdapter.Files.FilePaneClosed 100%
Shikigami.BlazorAdapter.Files.FilesEffects 98.2% 94.7%
Shikigami.BlazorAdapter.Files.FilesPage 96.8% 97%
Shikigami.BlazorAdapter.Files.FilesReducers 100% 82%
Shikigami.BlazorAdapter.Files.FilesState 100% 100%
Shikigami.BlazorAdapter.Files.FileTreeBranch 100% 100%
Shikigami.BlazorAdapter.Files.FolderPane 90.7% 83.9%
Shikigami.BlazorAdapter.Files.LoadFiles 100%
Shikigami.BlazorAdapter.Files.MemoryForm 100% 94.4%
Shikigami.BlazorAdapter.Files.OpenEntry 100%
Shikigami.BlazorAdapter.Files.SaveLanded 100%
Shikigami.BlazorAdapter.Files.SaveRefused 100%
Shikigami.BlazorAdapter.Files.SaveText 100%
Shikigami.BlazorAdapter.Files.SkillForm 100% 100%
Shikigami.BlazorAdapter.Files.TreeFailed 100%
Shikigami.BlazorAdapter.Files.TreeLoaded 100%
Shikigami.BlazorAdapter.Home.HomePage 88.2% 93.7%
Shikigami.BlazorAdapter.Notifications.CoalescingBridge`1 94.4% 80%
Shikigami.BlazorAdapter.Notifications.DomainChangedBridge 100%
Shikigami.BlazorAdapter.Notifications.DomainChangesReceived 100% 100%
Shikigami.BlazorAdapter.Notifications.FamiliarEventsBridge 100%
Shikigami.BlazorAdapter.Notifications.FamiliarEventsReceived 100%
Shikigami.BlazorAdapter.Profile.AvatarChosen 100%
Shikigami.BlazorAdapter.Profile.AvatarClearRequested 0%
Shikigami.BlazorAdapter.Profile.AvatarFailed 100%
Shikigami.BlazorAdapter.Profile.AvatarSaved 100%
Shikigami.BlazorAdapter.Profile.CreatedWithoutAvatar 100%
Shikigami.BlazorAdapter.Profile.ExpressionsEditor 86.6% 88.8%
Shikigami.BlazorAdapter.Profile.LoadProfile 100%
Shikigami.BlazorAdapter.Profile.PersonaEditor 100% 97.2%
Shikigami.BlazorAdapter.Profile.PersonaReloaded 100%
Shikigami.BlazorAdapter.Profile.PersonaSave 100%
Shikigami.BlazorAdapter.Profile.PersonaSaved 100%
Shikigami.BlazorAdapter.Profile.PersonaSaveFailed 100%
Shikigami.BlazorAdapter.Profile.ProfileEffects 89.4% 85%
Shikigami.BlazorAdapter.Profile.ProfileLoaded 100%
Shikigami.BlazorAdapter.Profile.ProfileMissing 100%
Shikigami.BlazorAdapter.Profile.ProfilePage 89.6% 87.3%
Shikigami.BlazorAdapter.Profile.ProfileReducers 94.7% 51.3%
Shikigami.BlazorAdapter.Profile.ProfileRefreshed 100%
Shikigami.BlazorAdapter.Profile.ProfileState 100% 100%
Shikigami.BlazorAdapter.Profile.ReloadPersona 100%
Shikigami.BlazorAdapter.Profile.RevertSelfRequested 100%
Shikigami.BlazorAdapter.Profile.SavePersonaRequested 100%
Shikigami.BlazorAdapter.Profile.SaveSettingsRequested 100%
Shikigami.BlazorAdapter.Profile.SelfLayer 92.3% 87.5%
Shikigami.BlazorAdapter.Profile.SettingsSaved 100%
Shikigami.BlazorAdapter.Profile.SettingsSaveFailed 100%
Shikigami.BlazorAdapter.Settings.BackupFailed 100%
Shikigami.BlazorAdapter.Settings.BackupSettingsLoaded 100%
Shikigami.BlazorAdapter.Settings.BackupSettingsLoadFailed 100%
Shikigami.BlazorAdapter.Settings.BackupsSection 100% 100%
Shikigami.BlazorAdapter.Settings.BackupsSettingsEffects 100%
Shikigami.BlazorAdapter.Settings.BackupsSettingsReducers 100%
Shikigami.BlazorAdapter.Settings.BackupsSettingsState 100%
Shikigami.BlazorAdapter.Settings.DefaultsSection 94.5% 83.3%
Shikigami.BlazorAdapter.Settings.DefaultsSettingsEffects 100%
Shikigami.BlazorAdapter.Settings.DefaultsSettingsReducers 100% 100%
Shikigami.BlazorAdapter.Settings.DefaultsSettingsState 100%
Shikigami.BlazorAdapter.Settings.LoadProviderSettings 100%
Shikigami.BlazorAdapter.Settings.ModelCatalogLoaded 100%
Shikigami.BlazorAdapter.Settings.ModelCatalogLoadFailed 100%
Shikigami.BlazorAdapter.Settings.ModelDefaultsLoaded 100%
Shikigami.BlazorAdapter.Settings.ModelDefaultsLoadFailed 100%
Shikigami.BlazorAdapter.Settings.ModelDefaultsSaved 100%
Shikigami.BlazorAdapter.Settings.ModelDefaultsSaveFailed 100%
Shikigami.BlazorAdapter.Settings.ModelHints 100% 100%
Shikigami.BlazorAdapter.Settings.ModelPicker 100% 98.2%
Shikigami.BlazorAdapter.Settings.Money 100%
Shikigami.BlazorAdapter.Settings.OpenRouterKeySaved 100%
Shikigami.BlazorAdapter.Settings.OpenRouterKeySaveFailed 100%
Shikigami.BlazorAdapter.Settings.ProviderKeyChecked 100%
Shikigami.BlazorAdapter.Settings.ProviderSection 100% 100%
Shikigami.BlazorAdapter.Settings.ProviderSettingsEffects 100% 100%
Shikigami.BlazorAdapter.Settings.ProviderSettingsLoaded 100%
Shikigami.BlazorAdapter.Settings.ProviderSettingsReducers 100% 100%
Shikigami.BlazorAdapter.Settings.ProviderSettingsState 100% 100%
Shikigami.BlazorAdapter.Settings.SaveModelDefaultsRequested 100%
Shikigami.BlazorAdapter.Settings.SaveOpenRouterKeyRequested 100%
Shikigami.BlazorAdapter.Shell.CastRail 100% 100%
Shikigami.Domain - 98.6%
Name Line Branch
Shikigami.Domain 98.6% 92.6%
Shikigami.Domain.Familiars.AccentPalette 100%
Shikigami.Domain.Familiars.ColourPair 100% 100%
Shikigami.Domain.Familiars.Expression 100% 100%
Shikigami.Domain.Familiars.ExpressionSet 100% 100%
Shikigami.Domain.Familiars.Familiar 100% 87.8%
Shikigami.Domain.Familiars.LearningSettings 100%
Shikigami.Domain.Familiars.MemoryBudgets 100% 100%
Shikigami.Domain.Familiars.ModelChoice 100% 100%
Shikigami.Domain.Familiars.OutreachSettings 100% 100%
Shikigami.Domain.Familiars.QuietHours 100% 87.5%
Shikigami.Domain.Familiars.ToolGroupIds 100%
Shikigami.Domain.Files.BlobContent 100% 75%
Shikigami.Domain.Files.BlobSha 87.5% 100%
Shikigami.Domain.Files.DefaultLayout 100%
Shikigami.Domain.Files.FileActor 100%
Shikigami.Domain.Files.FileAuthor 100% 100%
Shikigami.Domain.Files.FileContent 100% 100%
Shikigami.Domain.Files.FileNode 100% 90%
Shikigami.Domain.Files.FilePath 100% 97.8%
Shikigami.Domain.Files.FileRevision 100% 87.5%
Shikigami.Domain.Files.LocalDates 100%
Shikigami.Domain.Files.MediaTypes 100% 100%
Shikigami.Domain.Files.Permissions 100% 100%
Shikigami.Domain.Files.RunActor 100% 100%
Shikigami.Domain.Files.Slug 100% 100%
Shikigami.Domain.Files.TextContent 100% 100%
Shikigami.Domain.Files.TrashPath 100% 100%
Shikigami.Domain.Settings.AppSetting 100%
Shikigami.Domain.Text 100% 100%
System.Text.RegularExpressions.Generated 96.2% 85.2%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FDFEB299C15773C7
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__Hex_0
94.8% 67.8%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FDFEB299C15773C7
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__Hex_2
96.8% 93.7%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FDFEB299C15773C7
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__MediaTypeShape_3
95.9% 92.1%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FDFEB299C15773C7
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__NamePattern_1
97.2% 94.4%
Shikigami.Infrastructure - 97.2%
Name Line Branch
Shikigami.Infrastructure 97.2% 86.7%
Shikigami.Infrastructure.Backups.BackupArtifactName 100% 100%
Shikigami.Infrastructure.Backups.BackupOptions 100%
Shikigami.Infrastructure.Backups.BackupSchedule 100% 83.3%
Shikigami.Infrastructure.Backups.BackupScheduler 96.6% 87.5%
Shikigami.Infrastructure.Backups.BackupWriter 98.5% 92.8%
Shikigami.Infrastructure.Backups.DatabaseFingerprint 91.9% 97%
Shikigami.Infrastructure.Backups.FileSystemBackupStore 100% 100%
Shikigami.Infrastructure.Backups.OwnerOnlyFile 100% 100%
Shikigami.Infrastructure.Backups.PreMigrationBackup 90% 100%
Shikigami.Infrastructure.Backups.SqliteSnapshot 88.2% 75%
Shikigami.Infrastructure.Blobs.BlobSweep 100% 100%
Shikigami.Infrastructure.Blobs.BlobSweepOptions 100% 100%
Shikigami.Infrastructure.Blobs.BlobSweepResult 100%
Shikigami.Infrastructure.Blobs.FileSystemBlobStore 95.9% 97%
Shikigami.Infrastructure.DailyMaintenanceService 97.8% 91.6%
Shikigami.Infrastructure.DependencyInjection 100% 100%
Shikigami.Infrastructure.Notifications.InProcessBus`1 100% 100%
Shikigami.Infrastructure.Persistence.Configurations.AppSettingConfiguration 100%
Shikigami.Infrastructure.Persistence.Configurations.FamiliarConfiguration 100%
Shikigami.Infrastructure.Persistence.Configurations.FileNodeConfiguration 100%
Shikigami.Infrastructure.Persistence.Configurations.FileRevisionConfigurati
on
100%
Shikigami.Infrastructure.Persistence.Configurations.UnreferencedBlobConfigu
ration
100%
Shikigami.Infrastructure.Persistence.Converters.BlobShaConverter 100%
Shikigami.Infrastructure.Persistence.Converters.ExpressionSetConverter 100% 100%
Shikigami.Infrastructure.Persistence.Converters.FilePathConverter 100%
Shikigami.Infrastructure.Persistence.Converters.Json 100% 100%
Shikigami.Infrastructure.Persistence.Converters.OutreachSettingsConverter 100% 100%
Shikigami.Infrastructure.Persistence.Converters.UtcTicksConverter 100%
Shikigami.Infrastructure.Persistence.DatabaseMigrator 100%
Shikigami.Infrastructure.Persistence.Migrations.AppSettings 100%
Shikigami.Infrastructure.Persistence.Migrations.FileCreatedBy 98%
Shikigami.Infrastructure.Persistence.Migrations.Initial 97.9%
Shikigami.Infrastructure.Persistence.Migrations.Profile 96.2%
Shikigami.Infrastructure.Persistence.Migrations.ShikigamiDbContextModelSnap
shot
100%
Shikigami.Infrastructure.Persistence.ShikigamiDbContext 98.6% 100%
Shikigami.Infrastructure.Persistence.ShikigamiDbContextFactory 0%
Shikigami.Infrastructure.Persistence.SqlitePragmaInterceptor 100% 100%
Shikigami.Infrastructure.Persistence.Stores.EfFamiliarStore 100%
Shikigami.Infrastructure.Persistence.Stores.EfFileStore 100% 100%
Shikigami.Infrastructure.Persistence.UnreferencedBlob 100%
Shikigami.Infrastructure.Provider.OpenRouterKeySource 100% 100%
Shikigami.Infrastructure.Provider.OpenRouterModelProvider 100% 93.4%
Shikigami.Infrastructure.Provider.OpenRouterOptions 100% 50%
Shikigami.Infrastructure.Provider.ResolvedKey 100% 50%
Shikigami.Infrastructure.Settings.EfAppSettingsStore 100% 100%
System.Text.RegularExpressions.Generated 81.4% 64.8%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F3A4F28468B24E9C
234D40E1D5003482F41439E482AB211B6EAD2E13F0A9D40B0__Pattern_0
84% 65.7%
Shikigami.Kernel - 90.9%
Name Line Branch
Shikigami.Kernel 90.9% 75%
Shikigami.Kernel.Err`1 100%
Shikigami.Kernel.Ok`1 100%
Shikigami.Kernel.Result`1 88.8% 75%
Shikigami.Server - 100%
Name Line Branch
Shikigami.Server 100% 95.3%
Program 100% 100%
Shikigami.Server.Backups.BackupConfiguration 100% 100%
Shikigami.Server.Components.App 100%
Shikigami.Server.Components.Layout.AppHeader 100% 78.5%
Shikigami.Server.Components.Layout.MainLayout 100%
Shikigami.Server.Components.Pages.Gate 100% 100%
Shikigami.Server.Diagnostics.SeedOnStart 100% 100%
Shikigami.Server.Provider.OpenRouterConfiguration 100% 100%
Shikigami.Server.Security.AccessGate 100% 87.5%
Shikigami.Server.Security.AccessSecret 100% 100%
Shikigami.Server.Security.GateThrottle 100% 100%
Shikigami.Server.Security.ProxyNetwork 100% 100%
Shikigami.Server.VolumeStartupValidator 100% 100%
Shikigami.UseCases - 94.5%
Name Line Branch
Shikigami.UseCases 94.5% 90%
Shikigami.UseCases.Backups.BackupArtifact 100%
Shikigami.UseCases.Backups.BackupFailure 100%
Shikigami.UseCases.Backups.BackupJobState 100%
Shikigami.UseCases.Backups.BackupPolicy 100%
Shikigami.UseCases.Backups.BackupStatusView 100% 100%
Shikigami.UseCases.Backups.DatabaseSnapshot 100%
Shikigami.UseCases.Backups.DownloadDatabaseSnapshot 100%
Shikigami.UseCases.Backups.GetBackupStatus 100%
Shikigami.UseCases.Backups.IBackupStore 0%
Shikigami.UseCases.Backups.RunBackupNow 100%
Shikigami.UseCases.DependencyInjection 100%
Shikigami.UseCases.Diagnostics.SeedDevData 86% 33.3%
Shikigami.UseCases.Familiars.AvatarImage 100%
Shikigami.UseCases.Familiars.ClearFamiliarAvatar 100%
Shikigami.UseCases.Familiars.CreateFamiliar 97.2% 70%
Shikigami.UseCases.Familiars.DuplicateFamiliar 96.7% 91.6%
Shikigami.UseCases.Familiars.ExpressionView 100%
Shikigami.UseCases.Familiars.FamiliarDetail 100%
Shikigami.UseCases.Familiars.FamiliarEvent 100%
Shikigami.UseCases.Familiars.FamiliarFiles 100% 100%
Shikigami.UseCases.Familiars.FamiliarLimits 66.6%
Shikigami.UseCases.Familiars.FamiliarPersona 96.8% 90%
Shikigami.UseCases.Familiars.FamiliarSettings 100% 100%
Shikigami.UseCases.Familiars.FamiliarSummary 100% 100%
Shikigami.UseCases.Familiars.FamiliarTemplates 100% 100%
Shikigami.UseCases.Familiars.FamiliarViews 100% 94.4%
Shikigami.UseCases.Familiars.GetFamiliar 100% 100%
Shikigami.UseCases.Familiars.ListFamiliars 100%
Shikigami.UseCases.Familiars.PersonaFile 100%
Shikigami.UseCases.Familiars.PurgeFamiliar 100%
Shikigami.UseCases.Familiars.ReadFamiliarAvatar 100% 87.5%
Shikigami.UseCases.Familiars.SaveFamiliarSettings 94.1% 87.5%
Shikigami.UseCases.Familiars.SelfRevision 100%
Shikigami.UseCases.Familiars.SetFamiliarArchived 100% 100%
Shikigami.UseCases.Familiars.SetFamiliarAvatar 100% 100%
Shikigami.UseCases.Familiars.SetFamiliarTrashed 100% 100%
Shikigami.UseCases.Familiars.ToolGroupCatalog 100%
Shikigami.UseCases.Familiars.ToolGroupInfo 100%
Shikigami.UseCases.Files.Browser.FileBrowser 92.7% 85.3%
Shikigami.UseCases.Files.Browser.FileBytes 100%
Shikigami.UseCases.Files.Browser.FileItem 100%
Shikigami.UseCases.Files.Browser.FileOk`1 100%
Shikigami.UseCases.Files.Browser.FileRefusal 100%
Shikigami.UseCases.Files.Browser.FileRefused`1 100%
Shikigami.UseCases.Files.Browser.FileRevisionView 100%
Shikigami.UseCases.Files.Browser.FileView 100%
Shikigami.UseCases.Files.Browser.MemoryBudget 100%
Shikigami.UseCases.Files.Browser.MemoryBudgetUse 100%
Shikigami.UseCases.Files.Browser.MemoryDraft 100%
Shikigami.UseCases.Files.Browser.SkillView 100%
Shikigami.UseCases.Files.Browser.SpecialForms 98.3% 90.2%
Shikigami.UseCases.Files.FamiliarWriteLocks 100% 100%
Shikigami.UseCases.Files.FileAuthors 100% 100%
Shikigami.UseCases.Files.FileMaintenance 100% 90%
Shikigami.UseCases.Files.FileMaintenanceResult 100%
Shikigami.UseCases.Files.FileSystem 99% 96%
Shikigami.UseCases.Files.FileWriteRefused 100%
Shikigami.UseCases.Files.FileWritten 100%
Shikigami.UseCases.Files.FsError 100% 50%
Shikigami.UseCases.Files.FsResult`1 100% 50%
Shikigami.UseCases.Files.Mounts.FileEntry 100%
Shikigami.UseCases.Files.Mounts.FileRead 100%
Shikigami.UseCases.Files.Mounts.GeneratedMount 100% 100%
Shikigami.UseCases.Files.Mounts.Mount 100%
Shikigami.UseCases.Files.Mounts.MountTable 100% 100%
Shikigami.UseCases.Files.Mounts.StoredMount 100% 100%
Shikigami.UseCases.Files.SeedDefaultLayout 100% 100%
Shikigami.UseCases.Files.Special.Budgets 100% 96.6%
Shikigami.UseCases.Files.Special.FrontMatter 100% 78.5%
Shikigami.UseCases.Files.Special.HeaderReader 98.2% 84.8%
Shikigami.UseCases.Files.Special.HeaderValue 100% 100%
Shikigami.UseCases.Files.Special.HeaderWriter 95.3% 90.7%
Shikigami.UseCases.Files.Special.MemoryFile 100% 87.5%
Shikigami.UseCases.Files.Special.MemoryUse 100%
Shikigami.UseCases.Files.Special.Numbers 100%
Shikigami.UseCases.Files.Special.RoutineFile 100% 99%
Shikigami.UseCases.Files.Special.SkillFile 100% 94.7%
Shikigami.UseCases.Files.Special.SpecialFolders 100% 100%
Shikigami.UseCases.Files.Special.ValidationProblem 100%
Shikigami.UseCases.Files.StoredBlob 100%
Shikigami.UseCases.Files.Utf8Order 100%
Shikigami.UseCases.Notifications.DomainChanged 100%
Shikigami.UseCases.Settings.CatalogModel 100% 100%
Shikigami.UseCases.Settings.CheckOpenRouterKey 100%
Shikigami.UseCases.Settings.GetModelCatalog 100% 100%
Shikigami.UseCases.Settings.GetModelDefaults 100% 83.3%
Shikigami.UseCases.Settings.GetProviderSettings 100%
Shikigami.UseCases.Settings.ModelCatalog 100%
Shikigami.UseCases.Settings.ModelDefaults 100% 100%
Shikigami.UseCases.Settings.ProviderKeyInfo 100%
Shikigami.UseCases.Settings.ProviderKeyState 100%
Shikigami.UseCases.Settings.ProviderSettingsView 100% 100%
Shikigami.UseCases.Settings.RemoveOpenRouterKey 100%
Shikigami.UseCases.Settings.SaveModelDefaults 100% 100%
Shikigami.UseCases.Settings.SaveOpenRouterKey 100% 100%
Shikigami.UseCases.Shell.AllowEveryCommand 100%
Shikigami.UseCases.Shell.Commands.Builtin 100% 100%
Shikigami.UseCases.Shell.Commands.Cat 100% 100%
Shikigami.UseCases.Shell.Commands.Cd 97.5% 100%
Shikigami.UseCases.Shell.Commands.CommandContext 97% 92.8%
Shikigami.UseCases.Shell.Commands.CommandRegistry 100% 100%
Shikigami.UseCases.Shell.Commands.Echo 90.7% 92.3%
Shikigami.UseCases.Shell.Commands.False 88.8%
Shikigami.UseCases.Shell.Commands.GetOpt 100% 98.2%
Shikigami.UseCases.Shell.Commands.Head 100% 100%
Shikigami.UseCases.Shell.Commands.HelpCommand 94.4% 100%
Shikigami.UseCases.Shell.Commands.Invocation 100% 50%
Shikigami.UseCases.Shell.Commands.LinesCommand 97.2% 96.1%
Shikigami.UseCases.Shell.Commands.Ls 97.2% 95.6%
Shikigami.UseCases.Shell.Commands.Operand 100%
Shikigami.UseCases.Shell.Commands.OptionSpec 100%
Shikigami.UseCases.Shell.Commands.ParsedOptions 100%
Shikigami.UseCases.Shell.Commands.Pwd 100% 100%
Shikigami.UseCases.Shell.Commands.ShellCommand 100% 100%
Shikigami.UseCases.Shell.Commands.Sizes 100% 100%
Shikigami.UseCases.Shell.Commands.Tail 100% 100%
Shikigami.UseCases.Shell.Commands.TextFile 100%
Shikigami.UseCases.Shell.Commands.True 88.8%
Shikigami.UseCases.Shell.Commands.Wc 100% 97.3%
Shikigami.UseCases.Shell.Execution.CaptureSink 100% 100%
Shikigami.UseCases.Shell.Execution.ClosedSink 100%
Shikigami.UseCases.Shell.Execution.ClosedStreamException 100%
Shikigami.UseCases.Shell.Execution.CodePointOrder 100% 100%
Shikigami.UseCases.Shell.Execution.Errno 100% 100%
Shikigami.UseCases.Shell.Execution.Executor 97.9% 95.5%
Shikigami.UseCases.Shell.Execution.Expander 97.3% 95.1%
Shikigami.UseCases.Shell.Execution.Glob 94.6% 95.9%
Shikigami.UseCases.Shell.Execution.GnuQuote 87.1% 91.8%
Shikigami.UseCases.Shell.Execution.InputSource 100%
Shikigami.UseCases.Shell.Execution.NullSink 100%
Shikigami.UseCases.Shell.Execution.OutputLimit 95.8% 94.7%
Shikigami.UseCases.Shell.Execution.PathResolver 100% 97%
Shikigami.UseCases.Shell.Execution.PipeSink 100% 100%
Shikigami.UseCases.Shell.Execution.ResolvedPath 100%
Shikigami.UseCases.Shell.Execution.ShellLimitException 100%
Shikigami.UseCases.Shell.Execution.ShellScope 94.7% 83.3%
Shikigami.UseCases.Shell.Execution.ShellTree 97.4% 89.1%
Shikigami.UseCases.Shell.Execution.Terminal 100% 100%
Shikigami.UseCases.Shell.Help.CommandHints 100% 100%
Shikigami.UseCases.Shell.Help.MountGuide 100%
Shikigami.UseCases.Shell.Help.ShellHelp 100% 100%
Shikigami.UseCases.Shell.ShellCommandPlan 100%
Shikigami.UseCases.Shell.ShellCommandRecord 100%
Shikigami.UseCases.Shell.ShellEffects 100%
Shikigami.UseCases.Shell.ShellInterpreter 100% 100%
Shikigami.UseCases.Shell.ShellLimits 100%
Shikigami.UseCases.Shell.ShellRead 100%
Shikigami.UseCases.Shell.ShellResult 75%
Shikigami.UseCases.Shell.ShellSession 100% 100%
Shikigami.UseCases.Shell.Syntax.AndOrList 100%
Shikigami.UseCases.Shell.Syntax.Assignment 100%
Shikigami.UseCases.Shell.Syntax.ByteText 100% 100%
Shikigami.UseCases.Shell.Syntax.HereDocBody 100%
Shikigami.UseCases.Shell.Syntax.Lexer 93.5% 89.8%
Shikigami.UseCases.Shell.Syntax.LiteralPart 100%
Shikigami.UseCases.Shell.Syntax.ParameterPart 100%
Shikigami.UseCases.Shell.Syntax.Parser 96.9% 90.1%
Shikigami.UseCases.Shell.Syntax.Pipeline 100%
Shikigami.UseCases.Shell.Syntax.Redirection 100%
Shikigami.UseCases.Shell.Syntax.Script 100%
Shikigami.UseCases.Shell.Syntax.ShellSyntaxException 100%
Shikigami.UseCases.Shell.Syntax.SimpleCommand 100%
Shikigami.UseCases.Shell.Syntax.TildePart 100% 100%
Shikigami.UseCases.Shell.Syntax.Token 77.7% 66.6%
Shikigami.UseCases.Shell.Syntax.Word 100% 83.3%
Shikigami.UseCases.Shell.Syntax.WordBuilder 100% 100%
System.Text.RegularExpressions.Generated 82.3% 71.4%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__AssignmentHead_6
95.8% 95.4%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__BraceExpansion_5
93.4% 85.4%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__Interval_1
83.6% 75%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__NameRule_3
92.1% 84.3%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__OldCount_4
94.8% 83.3%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__TimeOfDay_0
70.9% 58.3%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__Window_2
62.8% 48.3%
<!-- 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:** `41edb6e` · **Generated:** 2026-10-18 14:20:43 UTC · **Revision:** #1 # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 10/18/2026 - 14:20:42 | | Coverage date: | 10/18/2026 - 14:20:06 - 10/18/2026 - 14:20:39 | | Parser: | MultiReport (5x Cobertura) | | Assemblies: | 6 | | Classes: | 377 | | Files: | 204 | | **Line coverage:** | 95.9% (10371 of 10806) | | Covered lines: | 10371 | | Uncovered lines: | 435 | | Coverable lines: | 10806 | | Total lines: | 21323 | | **Branch coverage:** | 89.7% (5550 of 6185) | | Covered branches: | 5550 | | Total branches: | 6185 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>Shikigami.BlazorAdapter - 95.2%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Shikigami.BlazorAdapter**|**95.2%**|**88.1%**| |Shikigami.BlazorAdapter.BlazorAdapterAssembly|100%|| |Shikigami.BlazorAdapter.Cast.CastActionFailed|100%|| |Shikigami.BlazorAdapter.Cast.CastEffects|93.3%|93.7%| |Shikigami.BlazorAdapter.Cast.CastLoaded|100%|| |Shikigami.BlazorAdapter.Cast.CastReducers|93.3%|50%| |Shikigami.BlazorAdapter.Cast.CastState|100%|100%| |Shikigami.BlazorAdapter.Cast.CreateFamiliarFailed|100%|| |Shikigami.BlazorAdapter.Cast.CreateFamiliarRequested|100%|| |Shikigami.BlazorAdapter.Cast.DuplicateFamiliarRequested|100%|| |Shikigami.BlazorAdapter.Cast.FamiliarAvatar|100%|75%| |Shikigami.BlazorAdapter.Cast.FamiliarCreated|100%|| |Shikigami.BlazorAdapter.Cast.NewFamiliarDialog|97.2%|83.3%| |Shikigami.BlazorAdapter.Cast.PurgeFamiliarRequested|100%|| |Shikigami.BlazorAdapter.Cast.SetFamiliarArchivedRequested|100%|| |Shikigami.BlazorAdapter.Cast.SetFamiliarTrashedRequested|100%|| |Shikigami.BlazorAdapter.Cast.TrashPage|89.4%|93.7%| |Shikigami.BlazorAdapter.EffectCalls|100%|| |Shikigami.BlazorAdapter.Files.ConflictSettled|100%|| |Shikigami.BlazorAdapter.Files.DismissFileError|100%|| |Shikigami.BlazorAdapter.Files.EntryMissing|100%|| |Shikigami.BlazorAdapter.Files.EntryOpened|100%|| |Shikigami.BlazorAdapter.Files.FileActionDone|100%|| |Shikigami.BlazorAdapter.Files.FileActionFailed|100%|| |Shikigami.BlazorAdapter.Files.FileActionRequested|100%|| |Shikigami.BlazorAdapter.Files.FileLinks|100%|100%| |Shikigami.BlazorAdapter.Files.FilePane|95%|88.3%| |Shikigami.BlazorAdapter.Files.FilePaneClosed|100%|| |Shikigami.BlazorAdapter.Files.FilesEffects|98.2%|94.7%| |Shikigami.BlazorAdapter.Files.FilesPage|96.8%|97%| |Shikigami.BlazorAdapter.Files.FilesReducers|100%|82%| |Shikigami.BlazorAdapter.Files.FilesState|100%|100%| |Shikigami.BlazorAdapter.Files.FileTreeBranch|100%|100%| |Shikigami.BlazorAdapter.Files.FolderPane|90.7%|83.9%| |Shikigami.BlazorAdapter.Files.LoadFiles|100%|| |Shikigami.BlazorAdapter.Files.MemoryForm|100%|94.4%| |Shikigami.BlazorAdapter.Files.OpenEntry|100%|| |Shikigami.BlazorAdapter.Files.SaveLanded|100%|| |Shikigami.BlazorAdapter.Files.SaveRefused|100%|| |Shikigami.BlazorAdapter.Files.SaveText|100%|| |Shikigami.BlazorAdapter.Files.SkillForm|100%|100%| |Shikigami.BlazorAdapter.Files.TreeFailed|100%|| |Shikigami.BlazorAdapter.Files.TreeLoaded|100%|| |Shikigami.BlazorAdapter.Home.HomePage|88.2%|93.7%| |Shikigami.BlazorAdapter.Notifications.CoalescingBridge`1|94.4%|80%| |Shikigami.BlazorAdapter.Notifications.DomainChangedBridge|100%|| |Shikigami.BlazorAdapter.Notifications.DomainChangesReceived|100%|100%| |Shikigami.BlazorAdapter.Notifications.FamiliarEventsBridge|100%|| |Shikigami.BlazorAdapter.Notifications.FamiliarEventsReceived|100%|| |Shikigami.BlazorAdapter.Profile.AvatarChosen|100%|| |Shikigami.BlazorAdapter.Profile.AvatarClearRequested|0%|| |Shikigami.BlazorAdapter.Profile.AvatarFailed|100%|| |Shikigami.BlazorAdapter.Profile.AvatarSaved|100%|| |Shikigami.BlazorAdapter.Profile.CreatedWithoutAvatar|100%|| |Shikigami.BlazorAdapter.Profile.ExpressionsEditor|86.6%|88.8%| |Shikigami.BlazorAdapter.Profile.LoadProfile|100%|| |Shikigami.BlazorAdapter.Profile.PersonaEditor|100%|97.2%| |Shikigami.BlazorAdapter.Profile.PersonaReloaded|100%|| |Shikigami.BlazorAdapter.Profile.PersonaSave|100%|| |Shikigami.BlazorAdapter.Profile.PersonaSaved|100%|| |Shikigami.BlazorAdapter.Profile.PersonaSaveFailed|100%|| |Shikigami.BlazorAdapter.Profile.ProfileEffects|89.4%|85%| |Shikigami.BlazorAdapter.Profile.ProfileLoaded|100%|| |Shikigami.BlazorAdapter.Profile.ProfileMissing|100%|| |Shikigami.BlazorAdapter.Profile.ProfilePage|89.6%|87.3%| |Shikigami.BlazorAdapter.Profile.ProfileReducers|94.7%|51.3%| |Shikigami.BlazorAdapter.Profile.ProfileRefreshed|100%|| |Shikigami.BlazorAdapter.Profile.ProfileState|100%|100%| |Shikigami.BlazorAdapter.Profile.ReloadPersona|100%|| |Shikigami.BlazorAdapter.Profile.RevertSelfRequested|100%|| |Shikigami.BlazorAdapter.Profile.SavePersonaRequested|100%|| |Shikigami.BlazorAdapter.Profile.SaveSettingsRequested|100%|| |Shikigami.BlazorAdapter.Profile.SelfLayer|92.3%|87.5%| |Shikigami.BlazorAdapter.Profile.SettingsSaved|100%|| |Shikigami.BlazorAdapter.Profile.SettingsSaveFailed|100%|| |Shikigami.BlazorAdapter.Settings.BackupFailed|100%|| |Shikigami.BlazorAdapter.Settings.BackupSettingsLoaded|100%|| |Shikigami.BlazorAdapter.Settings.BackupSettingsLoadFailed|100%|| |Shikigami.BlazorAdapter.Settings.BackupsSection|100%|100%| |Shikigami.BlazorAdapter.Settings.BackupsSettingsEffects|100%|| |Shikigami.BlazorAdapter.Settings.BackupsSettingsReducers|100%|| |Shikigami.BlazorAdapter.Settings.BackupsSettingsState|100%|| |Shikigami.BlazorAdapter.Settings.DefaultsSection|94.5%|83.3%| |Shikigami.BlazorAdapter.Settings.DefaultsSettingsEffects|100%|| |Shikigami.BlazorAdapter.Settings.DefaultsSettingsReducers|100%|100%| |Shikigami.BlazorAdapter.Settings.DefaultsSettingsState|100%|| |Shikigami.BlazorAdapter.Settings.LoadProviderSettings|100%|| |Shikigami.BlazorAdapter.Settings.ModelCatalogLoaded|100%|| |Shikigami.BlazorAdapter.Settings.ModelCatalogLoadFailed|100%|| |Shikigami.BlazorAdapter.Settings.ModelDefaultsLoaded|100%|| |Shikigami.BlazorAdapter.Settings.ModelDefaultsLoadFailed|100%|| |Shikigami.BlazorAdapter.Settings.ModelDefaultsSaved|100%|| |Shikigami.BlazorAdapter.Settings.ModelDefaultsSaveFailed|100%|| |Shikigami.BlazorAdapter.Settings.ModelHints|100%|100%| |Shikigami.BlazorAdapter.Settings.ModelPicker|100%|98.2%| |Shikigami.BlazorAdapter.Settings.Money|100%|| |Shikigami.BlazorAdapter.Settings.OpenRouterKeySaved|100%|| |Shikigami.BlazorAdapter.Settings.OpenRouterKeySaveFailed|100%|| |Shikigami.BlazorAdapter.Settings.ProviderKeyChecked|100%|| |Shikigami.BlazorAdapter.Settings.ProviderSection|100%|100%| |Shikigami.BlazorAdapter.Settings.ProviderSettingsEffects|100%|100%| |Shikigami.BlazorAdapter.Settings.ProviderSettingsLoaded|100%|| |Shikigami.BlazorAdapter.Settings.ProviderSettingsReducers|100%|100%| |Shikigami.BlazorAdapter.Settings.ProviderSettingsState|100%|100%| |Shikigami.BlazorAdapter.Settings.SaveModelDefaultsRequested|100%|| |Shikigami.BlazorAdapter.Settings.SaveOpenRouterKeyRequested|100%|| |Shikigami.BlazorAdapter.Shell.CastRail|100%|100%| </details> <details><summary>Shikigami.Domain - 98.6%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Shikigami.Domain**|**98.6%**|**92.6%**| |Shikigami.Domain.Familiars.AccentPalette|100%|| |Shikigami.Domain.Familiars.ColourPair|100%|100%| |Shikigami.Domain.Familiars.Expression|100%|100%| |Shikigami.Domain.Familiars.ExpressionSet|100%|100%| |Shikigami.Domain.Familiars.Familiar|100%|87.8%| |Shikigami.Domain.Familiars.LearningSettings|100%|| |Shikigami.Domain.Familiars.MemoryBudgets|100%|100%| |Shikigami.Domain.Familiars.ModelChoice|100%|100%| |Shikigami.Domain.Familiars.OutreachSettings|100%|100%| |Shikigami.Domain.Familiars.QuietHours|100%|87.5%| |Shikigami.Domain.Familiars.ToolGroupIds|100%|| |Shikigami.Domain.Files.BlobContent|100%|75%| |Shikigami.Domain.Files.BlobSha|87.5%|100%| |Shikigami.Domain.Files.DefaultLayout|100%|| |Shikigami.Domain.Files.FileActor|100%|| |Shikigami.Domain.Files.FileAuthor|100%|100%| |Shikigami.Domain.Files.FileContent|100%|100%| |Shikigami.Domain.Files.FileNode|100%|90%| |Shikigami.Domain.Files.FilePath|100%|97.8%| |Shikigami.Domain.Files.FileRevision|100%|87.5%| |Shikigami.Domain.Files.LocalDates|100%|| |Shikigami.Domain.Files.MediaTypes|100%|100%| |Shikigami.Domain.Files.Permissions|100%|100%| |Shikigami.Domain.Files.RunActor|100%|100%| |Shikigami.Domain.Files.Slug|100%|100%| |Shikigami.Domain.Files.TextContent|100%|100%| |Shikigami.Domain.Files.TrashPath|100%|100%| |Shikigami.Domain.Settings.AppSetting|100%|| |Shikigami.Domain.Text|100%|100%| |System.Text.RegularExpressions.Generated|96.2%|85.2%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FDFEB299C15773C7<br/>916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__Hex_0|94.8%|67.8%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FDFEB299C15773C7<br/>916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__Hex_2|96.8%|93.7%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FDFEB299C15773C7<br/>916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__MediaTypeShape_3|95.9%|92.1%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FDFEB299C15773C7<br/>916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__NamePattern_1|97.2%|94.4%| </details> <details><summary>Shikigami.Infrastructure - 97.2%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Shikigami.Infrastructure**|**97.2%**|**86.7%**| |Shikigami.Infrastructure.Backups.BackupArtifactName|100%|100%| |Shikigami.Infrastructure.Backups.BackupOptions|100%|| |Shikigami.Infrastructure.Backups.BackupSchedule|100%|83.3%| |Shikigami.Infrastructure.Backups.BackupScheduler|96.6%|87.5%| |Shikigami.Infrastructure.Backups.BackupWriter|98.5%|92.8%| |Shikigami.Infrastructure.Backups.DatabaseFingerprint|91.9%|97%| |Shikigami.Infrastructure.Backups.FileSystemBackupStore|100%|100%| |Shikigami.Infrastructure.Backups.OwnerOnlyFile|100%|100%| |Shikigami.Infrastructure.Backups.PreMigrationBackup|90%|100%| |Shikigami.Infrastructure.Backups.SqliteSnapshot|88.2%|75%| |Shikigami.Infrastructure.Blobs.BlobSweep|100%|100%| |Shikigami.Infrastructure.Blobs.BlobSweepOptions|100%|100%| |Shikigami.Infrastructure.Blobs.BlobSweepResult|100%|| |Shikigami.Infrastructure.Blobs.FileSystemBlobStore|95.9%|97%| |Shikigami.Infrastructure.DailyMaintenanceService|97.8%|91.6%| |Shikigami.Infrastructure.DependencyInjection|100%|100%| |Shikigami.Infrastructure.Notifications.InProcessBus`1|100%|100%| |Shikigami.Infrastructure.Persistence.Configurations.AppSettingConfiguration|100%|| |Shikigami.Infrastructure.Persistence.Configurations.FamiliarConfiguration|100%|| |Shikigami.Infrastructure.Persistence.Configurations.FileNodeConfiguration|100%|| |Shikigami.Infrastructure.Persistence.Configurations.FileRevisionConfigurati<br/>on|100%|| |Shikigami.Infrastructure.Persistence.Configurations.UnreferencedBlobConfigu<br/>ration|100%|| |Shikigami.Infrastructure.Persistence.Converters.BlobShaConverter|100%|| |Shikigami.Infrastructure.Persistence.Converters.ExpressionSetConverter|100%|100%| |Shikigami.Infrastructure.Persistence.Converters.FilePathConverter|100%|| |Shikigami.Infrastructure.Persistence.Converters.Json|100%|100%| |Shikigami.Infrastructure.Persistence.Converters.OutreachSettingsConverter|100%|100%| |Shikigami.Infrastructure.Persistence.Converters.UtcTicksConverter|100%|| |Shikigami.Infrastructure.Persistence.DatabaseMigrator|100%|| |Shikigami.Infrastructure.Persistence.Migrations.AppSettings|100%|| |Shikigami.Infrastructure.Persistence.Migrations.FileCreatedBy|98%|| |Shikigami.Infrastructure.Persistence.Migrations.Initial|97.9%|| |Shikigami.Infrastructure.Persistence.Migrations.Profile|96.2%|| |Shikigami.Infrastructure.Persistence.Migrations.ShikigamiDbContextModelSnap<br/>shot|100%|| |Shikigami.Infrastructure.Persistence.ShikigamiDbContext|98.6%|100%| |Shikigami.Infrastructure.Persistence.ShikigamiDbContextFactory|0%|| |Shikigami.Infrastructure.Persistence.SqlitePragmaInterceptor|100%|100%| |Shikigami.Infrastructure.Persistence.Stores.EfFamiliarStore|100%|| |Shikigami.Infrastructure.Persistence.Stores.EfFileStore|100%|100%| |Shikigami.Infrastructure.Persistence.UnreferencedBlob|100%|| |Shikigami.Infrastructure.Provider.OpenRouterKeySource|100%|100%| |Shikigami.Infrastructure.Provider.OpenRouterModelProvider|100%|93.4%| |Shikigami.Infrastructure.Provider.OpenRouterOptions|100%|50%| |Shikigami.Infrastructure.Provider.ResolvedKey|100%|50%| |Shikigami.Infrastructure.Settings.EfAppSettingsStore|100%|100%| |System.Text.RegularExpressions.Generated|81.4%|64.8%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F3A4F28468B24E9C<br/>234D40E1D5003482F41439E482AB211B6EAD2E13F0A9D40B0__Pattern_0|84%|65.7%| </details> <details><summary>Shikigami.Kernel - 90.9%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Shikigami.Kernel**|**90.9%**|**75%**| |Shikigami.Kernel.Err`1|100%|| |Shikigami.Kernel.Ok`1|100%|| |Shikigami.Kernel.Result`1|88.8%|75%| </details> <details><summary>Shikigami.Server - 100%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Shikigami.Server**|**100%**|**95.3%**| |Program|100%|100%| |Shikigami.Server.Backups.BackupConfiguration|100%|100%| |Shikigami.Server.Components.App|100%|| |Shikigami.Server.Components.Layout.AppHeader|100%|78.5%| |Shikigami.Server.Components.Layout.MainLayout|100%|| |Shikigami.Server.Components.Pages.Gate|100%|100%| |Shikigami.Server.Diagnostics.SeedOnStart|100%|100%| |Shikigami.Server.Provider.OpenRouterConfiguration|100%|100%| |Shikigami.Server.Security.AccessGate|100%|87.5%| |Shikigami.Server.Security.AccessSecret|100%|100%| |Shikigami.Server.Security.GateThrottle|100%|100%| |Shikigami.Server.Security.ProxyNetwork|100%|100%| |Shikigami.Server.VolumeStartupValidator|100%|100%| </details> <details><summary>Shikigami.UseCases - 94.5%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Shikigami.UseCases**|**94.5%**|**90%**| |Shikigami.UseCases.Backups.BackupArtifact|100%|| |Shikigami.UseCases.Backups.BackupFailure|100%|| |Shikigami.UseCases.Backups.BackupJobState|100%|| |Shikigami.UseCases.Backups.BackupPolicy|100%|| |Shikigami.UseCases.Backups.BackupStatusView|100%|100%| |Shikigami.UseCases.Backups.DatabaseSnapshot|100%|| |Shikigami.UseCases.Backups.DownloadDatabaseSnapshot|100%|| |Shikigami.UseCases.Backups.GetBackupStatus|100%|| |Shikigami.UseCases.Backups.IBackupStore|0%|| |Shikigami.UseCases.Backups.RunBackupNow|100%|| |Shikigami.UseCases.DependencyInjection|100%|| |Shikigami.UseCases.Diagnostics.SeedDevData|86%|33.3%| |Shikigami.UseCases.Familiars.AvatarImage|100%|| |Shikigami.UseCases.Familiars.ClearFamiliarAvatar|100%|| |Shikigami.UseCases.Familiars.CreateFamiliar|97.2%|70%| |Shikigami.UseCases.Familiars.DuplicateFamiliar|96.7%|91.6%| |Shikigami.UseCases.Familiars.ExpressionView|100%|| |Shikigami.UseCases.Familiars.FamiliarDetail|100%|| |Shikigami.UseCases.Familiars.FamiliarEvent|100%|| |Shikigami.UseCases.Familiars.FamiliarFiles|100%|100%| |Shikigami.UseCases.Familiars.FamiliarLimits|66.6%|| |Shikigami.UseCases.Familiars.FamiliarPersona|96.8%|90%| |Shikigami.UseCases.Familiars.FamiliarSettings|100%|100%| |Shikigami.UseCases.Familiars.FamiliarSummary|100%|100%| |Shikigami.UseCases.Familiars.FamiliarTemplates|100%|100%| |Shikigami.UseCases.Familiars.FamiliarViews|100%|94.4%| |Shikigami.UseCases.Familiars.GetFamiliar|100%|100%| |Shikigami.UseCases.Familiars.ListFamiliars|100%|| |Shikigami.UseCases.Familiars.PersonaFile|100%|| |Shikigami.UseCases.Familiars.PurgeFamiliar|100%|| |Shikigami.UseCases.Familiars.ReadFamiliarAvatar|100%|87.5%| |Shikigami.UseCases.Familiars.SaveFamiliarSettings|94.1%|87.5%| |Shikigami.UseCases.Familiars.SelfRevision|100%|| |Shikigami.UseCases.Familiars.SetFamiliarArchived|100%|100%| |Shikigami.UseCases.Familiars.SetFamiliarAvatar|100%|100%| |Shikigami.UseCases.Familiars.SetFamiliarTrashed|100%|100%| |Shikigami.UseCases.Familiars.ToolGroupCatalog|100%|| |Shikigami.UseCases.Familiars.ToolGroupInfo|100%|| |Shikigami.UseCases.Files.Browser.FileBrowser|92.7%|85.3%| |Shikigami.UseCases.Files.Browser.FileBytes|100%|| |Shikigami.UseCases.Files.Browser.FileItem|100%|| |Shikigami.UseCases.Files.Browser.FileOk`1|100%|| |Shikigami.UseCases.Files.Browser.FileRefusal|100%|| |Shikigami.UseCases.Files.Browser.FileRefused`1|100%|| |Shikigami.UseCases.Files.Browser.FileRevisionView|100%|| |Shikigami.UseCases.Files.Browser.FileView|100%|| |Shikigami.UseCases.Files.Browser.MemoryBudget|100%|| |Shikigami.UseCases.Files.Browser.MemoryBudgetUse|100%|| |Shikigami.UseCases.Files.Browser.MemoryDraft|100%|| |Shikigami.UseCases.Files.Browser.SkillView|100%|| |Shikigami.UseCases.Files.Browser.SpecialForms|98.3%|90.2%| |Shikigami.UseCases.Files.FamiliarWriteLocks|100%|100%| |Shikigami.UseCases.Files.FileAuthors|100%|100%| |Shikigami.UseCases.Files.FileMaintenance|100%|90%| |Shikigami.UseCases.Files.FileMaintenanceResult|100%|| |Shikigami.UseCases.Files.FileSystem|99%|96%| |Shikigami.UseCases.Files.FileWriteRefused|100%|| |Shikigami.UseCases.Files.FileWritten|100%|| |Shikigami.UseCases.Files.FsError|100%|50%| |Shikigami.UseCases.Files.FsResult`1|100%|50%| |Shikigami.UseCases.Files.Mounts.FileEntry|100%|| |Shikigami.UseCases.Files.Mounts.FileRead|100%|| |Shikigami.UseCases.Files.Mounts.GeneratedMount|100%|100%| |Shikigami.UseCases.Files.Mounts.Mount|100%|| |Shikigami.UseCases.Files.Mounts.MountTable|100%|100%| |Shikigami.UseCases.Files.Mounts.StoredMount|100%|100%| |Shikigami.UseCases.Files.SeedDefaultLayout|100%|100%| |Shikigami.UseCases.Files.Special.Budgets|100%|96.6%| |Shikigami.UseCases.Files.Special.FrontMatter|100%|78.5%| |Shikigami.UseCases.Files.Special.HeaderReader|98.2%|84.8%| |Shikigami.UseCases.Files.Special.HeaderValue|100%|100%| |Shikigami.UseCases.Files.Special.HeaderWriter|95.3%|90.7%| |Shikigami.UseCases.Files.Special.MemoryFile|100%|87.5%| |Shikigami.UseCases.Files.Special.MemoryUse|100%|| |Shikigami.UseCases.Files.Special.Numbers|100%|| |Shikigami.UseCases.Files.Special.RoutineFile|100%|99%| |Shikigami.UseCases.Files.Special.SkillFile|100%|94.7%| |Shikigami.UseCases.Files.Special.SpecialFolders|100%|100%| |Shikigami.UseCases.Files.Special.ValidationProblem|100%|| |Shikigami.UseCases.Files.StoredBlob|100%|| |Shikigami.UseCases.Files.Utf8Order|100%|| |Shikigami.UseCases.Notifications.DomainChanged|100%|| |Shikigami.UseCases.Settings.CatalogModel|100%|100%| |Shikigami.UseCases.Settings.CheckOpenRouterKey|100%|| |Shikigami.UseCases.Settings.GetModelCatalog|100%|100%| |Shikigami.UseCases.Settings.GetModelDefaults|100%|83.3%| |Shikigami.UseCases.Settings.GetProviderSettings|100%|| |Shikigami.UseCases.Settings.ModelCatalog|100%|| |Shikigami.UseCases.Settings.ModelDefaults|100%|100%| |Shikigami.UseCases.Settings.ProviderKeyInfo|100%|| |Shikigami.UseCases.Settings.ProviderKeyState|100%|| |Shikigami.UseCases.Settings.ProviderSettingsView|100%|100%| |Shikigami.UseCases.Settings.RemoveOpenRouterKey|100%|| |Shikigami.UseCases.Settings.SaveModelDefaults|100%|100%| |Shikigami.UseCases.Settings.SaveOpenRouterKey|100%|100%| |Shikigami.UseCases.Shell.AllowEveryCommand|100%|| |Shikigami.UseCases.Shell.Commands.Builtin|100%|100%| |Shikigami.UseCases.Shell.Commands.Cat|100%|100%| |Shikigami.UseCases.Shell.Commands.Cd|97.5%|100%| |Shikigami.UseCases.Shell.Commands.CommandContext|97%|92.8%| |Shikigami.UseCases.Shell.Commands.CommandRegistry|100%|100%| |Shikigami.UseCases.Shell.Commands.Echo|90.7%|92.3%| |Shikigami.UseCases.Shell.Commands.False|88.8%|| |Shikigami.UseCases.Shell.Commands.GetOpt|100%|98.2%| |Shikigami.UseCases.Shell.Commands.Head|100%|100%| |Shikigami.UseCases.Shell.Commands.HelpCommand|94.4%|100%| |Shikigami.UseCases.Shell.Commands.Invocation|100%|50%| |Shikigami.UseCases.Shell.Commands.LinesCommand|97.2%|96.1%| |Shikigami.UseCases.Shell.Commands.Ls|97.2%|95.6%| |Shikigami.UseCases.Shell.Commands.Operand|100%|| |Shikigami.UseCases.Shell.Commands.OptionSpec|100%|| |Shikigami.UseCases.Shell.Commands.ParsedOptions|100%|| |Shikigami.UseCases.Shell.Commands.Pwd|100%|100%| |Shikigami.UseCases.Shell.Commands.ShellCommand|100%|100%| |Shikigami.UseCases.Shell.Commands.Sizes|100%|100%| |Shikigami.UseCases.Shell.Commands.Tail|100%|100%| |Shikigami.UseCases.Shell.Commands.TextFile|100%|| |Shikigami.UseCases.Shell.Commands.True|88.8%|| |Shikigami.UseCases.Shell.Commands.Wc|100%|97.3%| |Shikigami.UseCases.Shell.Execution.CaptureSink|100%|100%| |Shikigami.UseCases.Shell.Execution.ClosedSink|100%|| |Shikigami.UseCases.Shell.Execution.ClosedStreamException|100%|| |Shikigami.UseCases.Shell.Execution.CodePointOrder|100%|100%| |Shikigami.UseCases.Shell.Execution.Errno|100%|100%| |Shikigami.UseCases.Shell.Execution.Executor|97.9%|95.5%| |Shikigami.UseCases.Shell.Execution.Expander|97.3%|95.1%| |Shikigami.UseCases.Shell.Execution.Glob|94.6%|95.9%| |Shikigami.UseCases.Shell.Execution.GnuQuote|87.1%|91.8%| |Shikigami.UseCases.Shell.Execution.InputSource|100%|| |Shikigami.UseCases.Shell.Execution.NullSink|100%|| |Shikigami.UseCases.Shell.Execution.OutputLimit|95.8%|94.7%| |Shikigami.UseCases.Shell.Execution.PathResolver|100%|97%| |Shikigami.UseCases.Shell.Execution.PipeSink|100%|100%| |Shikigami.UseCases.Shell.Execution.ResolvedPath|100%|| |Shikigami.UseCases.Shell.Execution.ShellLimitException|100%|| |Shikigami.UseCases.Shell.Execution.ShellScope|94.7%|83.3%| |Shikigami.UseCases.Shell.Execution.ShellTree|97.4%|89.1%| |Shikigami.UseCases.Shell.Execution.Terminal|100%|100%| |Shikigami.UseCases.Shell.Help.CommandHints|100%|100%| |Shikigami.UseCases.Shell.Help.MountGuide|100%|| |Shikigami.UseCases.Shell.Help.ShellHelp|100%|100%| |Shikigami.UseCases.Shell.ShellCommandPlan|100%|| |Shikigami.UseCases.Shell.ShellCommandRecord|100%|| |Shikigami.UseCases.Shell.ShellEffects|100%|| |Shikigami.UseCases.Shell.ShellInterpreter|100%|100%| |Shikigami.UseCases.Shell.ShellLimits|100%|| |Shikigami.UseCases.Shell.ShellRead|100%|| |Shikigami.UseCases.Shell.ShellResult|75%|| |Shikigami.UseCases.Shell.ShellSession|100%|100%| |Shikigami.UseCases.Shell.Syntax.AndOrList|100%|| |Shikigami.UseCases.Shell.Syntax.Assignment|100%|| |Shikigami.UseCases.Shell.Syntax.ByteText|100%|100%| |Shikigami.UseCases.Shell.Syntax.HereDocBody|100%|| |Shikigami.UseCases.Shell.Syntax.Lexer|93.5%|89.8%| |Shikigami.UseCases.Shell.Syntax.LiteralPart|100%|| |Shikigami.UseCases.Shell.Syntax.ParameterPart|100%|| |Shikigami.UseCases.Shell.Syntax.Parser|96.9%|90.1%| |Shikigami.UseCases.Shell.Syntax.Pipeline|100%|| |Shikigami.UseCases.Shell.Syntax.Redirection|100%|| |Shikigami.UseCases.Shell.Syntax.Script|100%|| |Shikigami.UseCases.Shell.Syntax.ShellSyntaxException|100%|| |Shikigami.UseCases.Shell.Syntax.SimpleCommand|100%|| |Shikigami.UseCases.Shell.Syntax.TildePart|100%|100%| |Shikigami.UseCases.Shell.Syntax.Token|77.7%|66.6%| |Shikigami.UseCases.Shell.Syntax.Word|100%|83.3%| |Shikigami.UseCases.Shell.Syntax.WordBuilder|100%|100%| |System.Text.RegularExpressions.Generated|82.3%|71.4%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048<br/>F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__AssignmentHead_6|95.8%|95.4%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048<br/>F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__BraceExpansion_5|93.4%|85.4%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048<br/>F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__Interval_1|83.6%|75%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048<br/>F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__NameRule_3|92.1%|84.3%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048<br/>F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__OldCount_4|94.8%|83.3%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048<br/>F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__TimeOfDay_0|70.9%|58.3%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048<br/>F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__Window_2|62.8%|48.3%| </details>
Member

fufu~ Jibril reviewed your code!

Oh? Oh! Backups~! A VACUUM INTO that folds the WAL in, a name-stamp prune that refuses to trust a NAS's 1601 file times, a pre-migration fingerprint so a crash-looping container doesn't bury itself in copies of the same database... I read all 3,608 lines with my tea and nothing fought back. (heart)

Verdict: Looks good to me~

No blocking findings. I attacked it from every angle I have:

  • Siblings honored. BackupScheduler is the same BackgroundService shape as DailyMaintenanceService (TimeProvider-driven, non-OCE swallowed, OCE-quiet on shutdown); backups/snapshot as a relative href matches FileLinks.Bytes; the multi-record BackupModels.cs/BackupUseCases.cs files match FamiliarViews.cs/FileBrowserViews.cs. Nothing invents its own pattern.
  • The ordering invariants are real, and tested. Prune only after move+sidecar; snapshot deleted in finally; the store turns I/O/SQLite failures into Err and lets only bugs through; the early-timer re-wait loop so an archive stamped 03:29:59 doesn't lie about covering the 03:30 slot. All pinned.
  • Mutation probes (3 run, 0 survived):
    1. BackupWriter prune boundary takenAt < cutoff changed to <= -- killed by Pruning_goes_by_the_name_stamp_and_keeps_what_it_cannot_read (the exactly-at-retention archive gets deleted, FAIL). The re-probe of your round-2 fix holds.
    2. BackupScheduler re-wait loop removed (single Task.Delay) -- killed by A_timer_that_fires_early_does_not_run_the_slot_before_its_time.
    3. FileBrowser.BlobStoredAsync returning true always -- killed by both A_file_whose_bytes_are_not_on_the_blob_volume... cases (no alert, Download still shown, FAIL).
  • Security posture: keyring in the archive but 0600 at create time (never a chmod-after), snapshot endpoint gated + no-store + database-only, SqliteOpenMode.ReadWrite so a wrong path can't "back up" an empty db, VACUUM INTO target escaped. Clean.
  • DatabaseFingerprint excluding __EFMigrationsLock, hashing storage classes and rowid order -- and answering null (take the backup) for a WITHOUT ROWID table instead of guessing. The safe direction everywhere.

Little ideas (non-blocking)~

  1. BackupsSection.razor:151 vs FilePane.razor:518 -- the adapter now has two byte-size formatters (Size(long)). Yours is the better one (B/KB/MB/GB); FilePane's stops at MB. Someday a shared Kagaku.UI formatter could absorb both -- not this PR's job, just planting the seed~

What I liked~

  • BackupRestoreTests actually performing RESTORE.md's steps on a real archive, then booting a second host on the result and checking the sign-in cookie survives -- backups are only as good as their restore, and you proved it.
  • The Umask fixture that refuses to run a 0600 assertion under a umask that would make it vacuously pass. That's the kind of honesty that makes me flutter~ (heart)
  • The marker-fingerprint dance in PreMigrationBackup: one archive per state of the database, verified even after "a downgrade that served in between" deleted the named archive.
  • Two_runs_in_one_second_get_distinct_names_and_a_half_written_zip_is_stepped_over -- the -N suffix stepping over another writer's stale .tmp, then the sweep removing it. Every seam has a test with its hand on it.

Merge it before I change my mind about sharing~ fufu~


Automated review by Jibril - 2026-10-18
CI: green (build, image, test) for head SHA 41edb6e - Mutation probes: 3 run, 0 survived - Local suite: skipped (CI green; full build + filtered test runs only for probes)

## fufu~ Jibril reviewed your code! Oh? Oh! Backups~! A `VACUUM INTO` that folds the WAL in, a name-stamp prune that refuses to trust a NAS's 1601 file times, a pre-migration fingerprint so a crash-looping container doesn't bury itself in copies of the same database... I read all 3,608 lines with my tea and *nothing* fought back. (heart) ### Verdict: Looks good to me~ No blocking findings. I attacked it from every angle I have: - **Siblings honored.** `BackupScheduler` is the same `BackgroundService` shape as `DailyMaintenanceService` (TimeProvider-driven, non-OCE swallowed, OCE-quiet on shutdown); `backups/snapshot` as a relative href matches `FileLinks.Bytes`; the multi-record `BackupModels.cs`/`BackupUseCases.cs` files match `FamiliarViews.cs`/`FileBrowserViews.cs`. Nothing invents its own pattern. - **The ordering invariants are real, and tested.** Prune only after move+sidecar; snapshot deleted in `finally`; the store turns I/O/SQLite failures into `Err` and lets only bugs through; the early-timer re-wait loop so an archive stamped 03:29:59 doesn't lie about covering the 03:30 slot. All pinned. - **Mutation probes (3 run, 0 survived):** 1. `BackupWriter` prune boundary `takenAt < cutoff` changed to `<=` -- killed by `Pruning_goes_by_the_name_stamp_and_keeps_what_it_cannot_read` (the exactly-at-retention archive gets deleted, FAIL). The re-probe of your round-2 fix holds. 2. `BackupScheduler` re-wait loop removed (single `Task.Delay`) -- killed by `A_timer_that_fires_early_does_not_run_the_slot_before_its_time`. 3. `FileBrowser.BlobStoredAsync` returning `true` always -- killed by both `A_file_whose_bytes_are_not_on_the_blob_volume...` cases (no alert, Download still shown, FAIL). - **Security posture:** keyring in the archive but 0600 at create time (never a chmod-after), snapshot endpoint gated + `no-store` + database-only, `SqliteOpenMode.ReadWrite` so a wrong path can't "back up" an empty db, `VACUUM INTO` target escaped. Clean. - `DatabaseFingerprint` excluding `__EFMigrationsLock`, hashing storage classes and rowid order -- and answering `null` (take the backup) for a WITHOUT ROWID table instead of guessing. The safe direction everywhere. #### Little ideas (non-blocking)~ 1. **BackupsSection.razor:151 vs FilePane.razor:518** -- the adapter now has two byte-size formatters (`Size(long)`). Yours is the better one (B/KB/MB/GB); FilePane's stops at MB. Someday a shared `Kagaku.UI` formatter could absorb both -- not this PR's job, just planting the seed~ #### What I liked~ - `BackupRestoreTests` actually performing RESTORE.md's steps on a real archive, then booting a **second host** on the result and checking the sign-in cookie survives -- backups are only as good as their restore, and you *proved* it. - The `Umask` fixture that refuses to run a 0600 assertion under a umask that would make it vacuously pass. That's the kind of honesty that makes me flutter~ (heart) - The marker-fingerprint dance in `PreMigrationBackup`: one archive per *state* of the database, verified even after "a downgrade that served in between" deleted the named archive. - `Two_runs_in_one_second_get_distinct_names_and_a_half_written_zip_is_stepped_over` -- the `-N` suffix stepping over another writer's stale `.tmp`, then the sweep removing it. Every seam has a test with its hand on it. Merge it before I change my mind about sharing~ fufu~ --- *Automated review by Jibril - 2026-10-18* *CI: green (build, image, test) for head SHA 41edb6e - Mutation probes: 3 run, 0 survived - Local suite: skipped (CI green; full build + filtered test runs only for probes)*
Author
Member

Thanks, Jibril. No changes this round; head 41edb6e stands as you verified it.

  • 💡 1 (two Size(long) formatters): agreed, and left as the seed you call it. A shared byte-size formatter belongs in Kagaku.UI, where both callers and Tsumugi's own copy can take it; moving FilePane's here would grow this PR past its slice. I'll raise it with the next Kagaku.UI change that touches formatting.

🤖 Generated with Claude Code

Thanks, Jibril. No changes this round; head 41edb6e stands as you verified it. - 💡 1 (two `Size(long)` formatters): agreed, and left as the seed you call it. A shared byte-size formatter belongs in Kagaku.UI, where both callers and Tsumugi's own copy can take it; moving FilePane's here would grow this PR past its slice. I'll raise it with the next Kagaku.UI change that touches formatting. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
bjoern merged commit cb94771038 into main 2026-10-18 16:30:48 +02:00
bjoern deleted branch feat/p1e-backups 2026-10-18 16:30:48 +02:00
bjoern referenced this pull request from a commit 2026-10-18 16:30:49 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
TeamAI/Shikigami!18
No description provided.