feat: /health proves the db directory can host a SQLite database #140

Merged
bjoern merged 3 commits from feat/health-proves-the-db-volume-can-host-sqlite into main 2026-09-18 18:15:17 +02:00
Member

What's in

  • GET /health now proves the db directory can host a SQLite database, not merely store bytes: after the plain-file probe, the directory flagged HostsDatabase (db only, set in ReadinessRegistration) gets a tiny .dm-health-<name>.db opened with Microsoft.Data.Sqlite; PRAGMA journal_mode=WAL must answer wal (any other answer fails with journal_mode stayed <mode>), a real create+insert transaction commits, then the file and its -wal/-shm/-journal sidecars are removed. This is the check that would have failed on first boot for the CIFS volume that passed readiness while every backup died with database is locked.
  • Every dataDirectories entry gains a nullable detail carrying the refusing party's own message (the filesystem's, SQLite's, or the kept journal mode), null when fine; CheckReadinessUseCase logs one warning per failing directory per poll. The SQLite probe is skipped once the plain probe already refused, so a chmod'd or missing db directory keeps the filesystem's message rather than a misleading SQLite Error 14.
  • writable stays the per-directory verdict (false when the SQLite probe fails), so the Docker HEALTHCHECK, deploy/smoke.sh and the DEPLOYMENT §7 jq recipe are unchanged. No new tunables.
  • ADR 0010 amended (readiness paragraph): why only the db volume gets the locking probe — a locking probe on the images/thumbnails/backups volumes would fail a perfectly good network share.
  • deploy/DEPLOYMENT.md §7 documents the stronger db probe, the detail field, an interpretation table for detail on db (filesystem message → §8.1 permissions/path; any SQLite Error N — 5, 8, 10 disk I/O error, 14 — or journal_mode stayed <mode> → the volume cannot host SQLite, move it to local storage) and the sidecar names an operator may find after a hard kill. README's /health sentence updated; backend/openapi/v1.json regenerated (only the new detail property on DataDirectoryHealthDto).
  • Implementation: SqliteHostingProbe, IProbeNaming/GuidProbeNaming (Infrastructure), DataDirectory.HostsDatabase and DataDirectoryReadiness.Detail (ApplicationCore records). Test support: LinuxFactAttribute (loud skip naming the case that pins the branch elsewhere, same stance as EnforcedFileModesData) and OpenFileDescriptors (/proc/self/fd reader).

Verification

Executed by the release gate on the pushed HEAD 561526f: scripts/verify.sh backend from the worktree, fresh — == verify OK (backend) ==, exit 0. Release build 0 errors; RestAdapter.Tests 647/647, Infrastructure.Tests 205/205, ApplicationCore.Tests 49/49, IntegrationTests 1/1. The fix round had run the same script to completion on the same tree with the same counts.

Layers exercised

  • REST: HealthEndpointIntegrationTests boot the real Program.cs host on a temp root (TestHost) and read /health bodies — new Health_ReportsUnhealthy_WhenTheDbDirectoryStoresFilesButCannotHostSqlite (rows "", -wal, -shm, -journal: a directory placed at the probe database's path or a sidecar path so the plain probe still succeeds and SQLite refuses for real), Health_HostsTheProbeDatabaseInDbOnly_NotInTheOtherThreeDirectories (obstacle in all four directories, only db must go unhealthy), Health_LeavesNoProbeFilesBehind (extended: .db and sidecars, plus the descriptor table on Linux), and detail asserted as null on healthy entries, non-empty on broken ones, and as the filesystem's message naming the refused path (not SQLite's) on the Disappears, LosesWritePermission and NamesBoth rows. OpenApiSnapshotTests pins the regenerated snapshot.
  • Use case: ReadinessUseCaseTests — HostsDatabase flag drives the probe, not the name db (a directory named db without the flag stays writable; a flagged one named images fails); one warning line naming directory, path and detail via a capturing logger; Detail null when healthy.
  • Persistence/filesystem: SqliteHostingProbeTests run real SqliteConnections on real files — pass on a WAL-capable directory with one committed row; the journal-mode refusal on SQLite's unix-dotfile VFS (honesty guards prove the pragma answers delete there while a plain write still succeeds); SQLite's refusal plus cleanup for each blocked path; the leftover -wal when -shm is blocked (first proven with the test's own connection); and Probe_HoldsNoDescriptorOpen_AfterItReturns ([LinuxFact], canary guard, /proc/self/fd).
  • UI: not touched and not exercised — the app does not consume /health.

Mutation probes (each: mutate source, build, run ReadinessUseCaseTests + SqliteHostingProbeTests + HealthEndpointIntegrationTests, restore with git checkout --; all went red as listed, unmutated 32/32 green across repeated runs):

  1. Remove the SQLite probe call in CheckReadinessUseCase.Refusal → red 5 (4 CannotHostSqlite rows + ProbesSqlite_WhereTheFlagSays…).
  2. Swallow the SqliteException as writable → red 11.
  3. Drop the sidecar cleanup loop → red 3 (exactly the -shm cases, where SQLite leaves a -wal behind). 3b. Drop the .db deletion → red 11 incl. Health_LeavesNoProbeFilesBehind.
  4. Ignore the pragma's answer → red 1 (Exercise_ReportsTheModeTheFileKept_WhenTheVolumeCannotDoWal).
  5. Endpoint passes null instead of d.Detail → red 16 (every unhealthy case of all six mechanisms).
  6. Match directory.Name != "db" instead of the flag → red 1; unflagging db → red 5.
  7. Drop the warning log → red 1.
  8. ReadinessRegistration: flag images/thumbnails/backups as HostsDatabase: true (and backups alone) → red 1: Health_HostsTheProbeDatabaseInDbOnly… ("/health reports 'images' as writable=False; expected True, because only 'db' was broken.").
  9. Pooling = false → true on the probe connection → red 2: Probe_HoldsNoDescriptorOpen… (3 (deleted) descriptors: .db, -wal, -shm) and Health_LeavesNoProbeFilesBehind (6 after two polls). Short-circuiting the OpenFileDescriptors helper to empty → red on the unit test's canary, so the check is not vacuous.
  10. Run the SQLite probe even after a plain refusal → red 3 (Disappears("db"), LosesWritePermission("db"), NamesBoth, detail became SQLite Error 14).
  11. RefusalForAPlainFile returns "refused" instead of ex.Message → red 9.
    All probes were re-run independently in a second review round with the same outcomes.

uid 0: unshare -r dotnet test --no-build on the three classes: 28 passed, 4 skipped — only the mode-dependent LosesWritePermission rows; every new SQLite row and the filesystem-detail assertions on Disappears ran green as uid 0.

Unverified or confirmed by reading only

  • The journal-mode refusal (PRAGMA journal_mode=WAL silently answering another mode) is pinned only at the unit level on the unix-dotfile VFS; no real CIFS/NFS mount was tested through the host, because every local filesystem does WAL and every path-level obstacle makes SQLite throw instead.
  • That the HEALTHCHECK, deploy/smoke.sh (4 entries, all writable) and the §7 jq recipe still work was confirmed by reading — writable's semantics are unchanged — not by running the container.
  • DEPLOYMENT §7's SQLite Error 10 example: a blocked -wal path gives Error 10 with the system SQLite locally; the CIFS IOERR_SHMMAP cause named in the parenthetical was not reproduced.
  • The descriptor tests are Linux-only by design (Probe_LeavesNothingBehind… catches the same fault on Windows, where an open file cannot be deleted); nothing was run on Windows.
  • A pre-existing teardown flake of HealthEndpointIntegrationTests (IOException: Directory not empty : '<root>/db', ~1 in 10 class runs, reproduces on main) was observed during review and recorded in BACKLOG; the recorded artefact (db/doujin-manager.db-shm) is narrower than one reproduction, which left an empty db/doujin-manager.db instead. Not caused by this branch; none of the verify runs hit it.

Follow-ups

  • docs/BACKLOG.md: added a Process item for the HealthEndpointIntegrationTests teardown flake (failure text, ~1-in-10 rate, reproduces on main, leaked db/doujin-manager.db-shm, the unverified app.Run()/CheckpointOnShutdown hypothesis, two candidate fixes — retry the root delete on ENOTEMPTY, or await the entry-point thread).
  • docs/BACKLOG.md: removed the two Backend items this branch ships (the /health SQLite-hosting probe; the /health 503 warning log, #112) and renamed one token in the uid-0 Process item (CheckReadinessUseCase.AcceptsWrites → RefusalForAPlainFile) to match the code.

🤖 Generated with Claude Code

## What's in - `GET /health` now proves the **db** directory can host a SQLite database, not merely store bytes: after the plain-file probe, the directory flagged `HostsDatabase` (db only, set in `ReadinessRegistration`) gets a tiny `.dm-health-<name>.db` opened with Microsoft.Data.Sqlite; `PRAGMA journal_mode=WAL` must answer `wal` (any other answer fails with `journal_mode stayed <mode>`), a real create+insert transaction commits, then the file and its `-wal`/`-shm`/`-journal` sidecars are removed. This is the check that would have failed on first boot for the CIFS volume that passed readiness while every backup died with `database is locked`. - Every `dataDirectories` entry gains a nullable `detail` carrying the refusing party's own message (the filesystem's, SQLite's, or the kept journal mode), `null` when fine; `CheckReadinessUseCase` logs one warning per failing directory per poll. The SQLite probe is skipped once the plain probe already refused, so a chmod'd or missing db directory keeps the filesystem's message rather than a misleading `SQLite Error 14`. - `writable` stays the per-directory verdict (false when the SQLite probe fails), so the Docker `HEALTHCHECK`, `deploy/smoke.sh` and the DEPLOYMENT §7 jq recipe are unchanged. No new tunables. - **ADR 0010 amended** (readiness paragraph): why only the db volume gets the locking probe — a locking probe on the images/thumbnails/backups volumes would fail a perfectly good network share. - `deploy/DEPLOYMENT.md` §7 documents the stronger db probe, the `detail` field, an interpretation table for `detail` on db (filesystem message → §8.1 permissions/path; any `SQLite Error N` — 5, 8, 10 `disk I/O error`, 14 — or `journal_mode stayed <mode>` → the volume cannot host SQLite, move it to local storage) and the sidecar names an operator may find after a hard kill. README's `/health` sentence updated; `backend/openapi/v1.json` regenerated (only the new `detail` property on `DataDirectoryHealthDto`). - Implementation: `SqliteHostingProbe`, `IProbeNaming`/`GuidProbeNaming` (Infrastructure), `DataDirectory.HostsDatabase` and `DataDirectoryReadiness.Detail` (ApplicationCore records). Test support: `LinuxFactAttribute` (loud skip naming the case that pins the branch elsewhere, same stance as `EnforcedFileModesData`) and `OpenFileDescriptors` (`/proc/self/fd` reader). ## Verification **Executed by the release gate on the pushed HEAD `561526f`:** `scripts/verify.sh backend` from the worktree, fresh — `== verify OK (backend) ==`, exit 0. Release build 0 errors; RestAdapter.Tests 647/647, Infrastructure.Tests 205/205, ApplicationCore.Tests 49/49, IntegrationTests 1/1. The fix round had run the same script to completion on the same tree with the same counts. **Layers exercised** - REST: `HealthEndpointIntegrationTests` boot the real `Program.cs` host on a temp root (TestHost) and read `/health` bodies — new `Health_ReportsUnhealthy_WhenTheDbDirectoryStoresFilesButCannotHostSqlite` (rows `""`, `-wal`, `-shm`, `-journal`: a directory placed at the probe database's path or a sidecar path so the plain probe still succeeds and SQLite refuses for real), `Health_HostsTheProbeDatabaseInDbOnly_NotInTheOtherThreeDirectories` (obstacle in all four directories, only db must go unhealthy), `Health_LeavesNoProbeFilesBehind` (extended: `.db` and sidecars, plus the descriptor table on Linux), and `detail` asserted as `null` on healthy entries, non-empty on broken ones, and as the filesystem's message naming the refused path (not SQLite's) on the `Disappears`, `LosesWritePermission` and `NamesBoth` rows. `OpenApiSnapshotTests` pins the regenerated snapshot. - Use case: `ReadinessUseCaseTests` — `HostsDatabase` flag drives the probe, not the name `db` (a directory named `db` without the flag stays writable; a flagged one named `images` fails); one warning line naming directory, path and detail via a capturing logger; `Detail` null when healthy. - Persistence/filesystem: `SqliteHostingProbeTests` run real `SqliteConnection`s on real files — pass on a WAL-capable directory with one committed row; the journal-mode refusal on SQLite's `unix-dotfile` VFS (honesty guards prove the pragma answers `delete` there while a plain write still succeeds); SQLite's refusal plus cleanup for each blocked path; the leftover `-wal` when `-shm` is blocked (first proven with the test's own connection); and `Probe_HoldsNoDescriptorOpen_AfterItReturns` (`[LinuxFact]`, canary guard, `/proc/self/fd`). - UI: not touched and not exercised — the app does not consume `/health`. **Mutation probes** (each: mutate source, build, run `ReadinessUseCaseTests` + `SqliteHostingProbeTests` + `HealthEndpointIntegrationTests`, restore with `git checkout --`; all went red as listed, unmutated 32/32 green across repeated runs): 1. Remove the SQLite probe call in `CheckReadinessUseCase.Refusal` → red 5 (4 `CannotHostSqlite` rows + `ProbesSqlite_WhereTheFlagSays…`). 2. Swallow the `SqliteException` as writable → red 11. 3. Drop the sidecar cleanup loop → red 3 (exactly the `-shm` cases, where SQLite leaves a `-wal` behind). 3b. Drop the `.db` deletion → red 11 incl. `Health_LeavesNoProbeFilesBehind`. 4. Ignore the pragma's answer → red 1 (`Exercise_ReportsTheModeTheFileKept_WhenTheVolumeCannotDoWal`). 5. Endpoint passes `null` instead of `d.Detail` → red 16 (every unhealthy case of all six mechanisms). 6. Match `directory.Name != "db"` instead of the flag → red 1; unflagging db → red 5. 7. Drop the warning log → red 1. 8. `ReadinessRegistration`: flag images/thumbnails/backups as `HostsDatabase: true` (and backups alone) → red 1: `Health_HostsTheProbeDatabaseInDbOnly…` ("/health reports 'images' as writable=False; expected True, because only 'db' was broken."). 9. `Pooling = false` → `true` on the probe connection → red 2: `Probe_HoldsNoDescriptorOpen…` (3 `(deleted)` descriptors: `.db`, `-wal`, `-shm`) and `Health_LeavesNoProbeFilesBehind` (6 after two polls). Short-circuiting the `OpenFileDescriptors` helper to empty → red on the unit test's canary, so the check is not vacuous. 10. Run the SQLite probe even after a plain refusal → red 3 (`Disappears("db")`, `LosesWritePermission("db")`, `NamesBoth`, detail became `SQLite Error 14`). 11. `RefusalForAPlainFile` returns `"refused"` instead of `ex.Message` → red 9. All probes were re-run independently in a second review round with the same outcomes. **uid 0:** `unshare -r dotnet test --no-build` on the three classes: 28 passed, 4 skipped — only the mode-dependent `LosesWritePermission` rows; every new SQLite row and the filesystem-detail assertions on `Disappears` ran green as uid 0. **Unverified or confirmed by reading only** - The journal-mode refusal (`PRAGMA journal_mode=WAL` silently answering another mode) is pinned only at the unit level on the `unix-dotfile` VFS; no real CIFS/NFS mount was tested through the host, because every local filesystem does WAL and every path-level obstacle makes SQLite throw instead. - That the `HEALTHCHECK`, `deploy/smoke.sh` (4 entries, all `writable`) and the §7 jq recipe still work was confirmed by reading — `writable`'s semantics are unchanged — not by running the container. - DEPLOYMENT §7's `SQLite Error 10` example: a blocked `-wal` path gives Error 10 with the system SQLite locally; the CIFS `IOERR_SHMMAP` cause named in the parenthetical was not reproduced. - The descriptor tests are Linux-only by design (`Probe_LeavesNothingBehind…` catches the same fault on Windows, where an open file cannot be deleted); nothing was run on Windows. - A pre-existing teardown flake of `HealthEndpointIntegrationTests` (`IOException: Directory not empty : '<root>/db'`, ~1 in 10 class runs, reproduces on `main`) was observed during review and recorded in BACKLOG; the recorded artefact (`db/doujin-manager.db-shm`) is narrower than one reproduction, which left an empty `db/doujin-manager.db` instead. Not caused by this branch; none of the verify runs hit it. ## Follow-ups - docs/BACKLOG.md: added a Process item for the `HealthEndpointIntegrationTests` teardown flake (failure text, ~1-in-10 rate, reproduces on `main`, leaked `db/doujin-manager.db-shm`, the unverified `app.Run()`/`CheckpointOnShutdown` hypothesis, two candidate fixes — retry the root delete on `ENOTEMPTY`, or await the entry-point thread). - docs/BACKLOG.md: removed the two Backend items this branch ships (the `/health` SQLite-hosting probe; the `/health` 503 warning log, #112) and renamed one token in the uid-0 Process item (`CheckReadinessUseCase.AcceptsWrites` → `RefusalForAPlainFile`) to match the code. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
The plain-file probe could not tell a volume that stores bytes from one that can lock a database, which is how a CIFS volume passed readiness on every boot while every backup failed with 'database is locked'. The directory flagged as hosting the database (db, set in ReadinessRegistration) now additionally hosts a tiny SQLite database per poll: WAL is required (any other journal_mode answer fails), a write transaction commits, and the file and its -wal/-shm/-journal sidecars are removed. Each /health entry gains a nullable 'detail' with the refusing party's own message, also logged once per failing directory; 'writable' stays the verdict so the HEALTHCHECK, smoke.sh and the jq recipe are unchanged. ADR 0010 amended; DEPLOYMENT.md section 7 documents the detail, the sidecar names and what a SQLite detail on db means; OpenAPI snapshot regenerated; backlog item removed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
test: pin the db-only scope, the unpooled probe connection and the filesystem's detail
Some checks failed
CI / build (pull_request) Has been cancelled
CI / test (pull_request) Has been cancelled
Deploy Smoke Test / smoke (pull_request) Has been cancelled
561526f924
Review round 1 of the /health SQLite probe. Health_HostsTheProbeDatabaseInDbOnly blocks the probe database's -shm path in all four directories, so a host that flagged another directory as hosting the database goes red. Probe_HoldsNoDescriptorOpen (unit, /proc/self/fd) and Health_LeavesNoProbeFilesBehind now read the descriptor table, so a pooled probe connection that unlinks its files without closing them goes red. The Disappears, LosesWritePermission and NamesBoth rows assert the detail names the refused path and is not SQLite's, pinning both that the plain refusal is reported in the filesystem's words and that the SQLite probe does not run after it. DEPLOYMENT §7's table now covers any SQLite error code (10 included); BACKLOG drops the shipped /health warning-log item and records the pre-existing teardown flake of HealthEndpointIntegrationTests.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
docs: name both forms of the leaked file in the health teardown-flake note
Some checks failed
CI / build (pull_request) Successful in 22s
Deploy Smoke Test / smoke (pull_request) Failing after 2m48s
CI / test (pull_request) Successful in 1m35s
856292a798
The second-round probe saw the flake leave an empty db/doujin-manager.db rather than the -shm file, so the note names either form of a freshly recreated live-database file.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Summary

Summary
Generated on: 09/18/2026 - 13:59:04
Coverage date: 09/18/2026 - 13:57:54 - 09/18/2026 - 13:59:01
Parser: MultiReport (4x Cobertura)
Assemblies: 4
Classes: 425
Files: 176
Line coverage: 94.6% (16290 of 17212)
Covered lines: 16290
Uncovered lines: 922
Coverable lines: 17212
Total lines: 26568
Branch coverage: 73.4% (1478 of 2011)
Covered branches: 1478
Total branches: 2011
Method coverage: Feature is only available for sponsors

Coverage

DoujinManager.ApplicationCore - 94.1%
Name Line Branch
DoujinManager.ApplicationCore 94.1% 100%
DoujinManager.ApplicationCore.Entities.Chapter 87.5%
DoujinManager.ApplicationCore.Entities.Character 100%
DoujinManager.ApplicationCore.Entities.Circle 100%
DoujinManager.ApplicationCore.Entities.Doujin 100%
DoujinManager.ApplicationCore.Entities.DoujinCharacter 75%
DoujinManager.ApplicationCore.Entities.DoujinCircle 75%
DoujinManager.ApplicationCore.Entities.DoujinPerson 80%
DoujinManager.ApplicationCore.Entities.DoujinSeries 75%
DoujinManager.ApplicationCore.Entities.DoujinTag 75%
DoujinManager.ApplicationCore.Entities.ImageFile 100%
DoujinManager.ApplicationCore.Entities.Page 80%
DoujinManager.ApplicationCore.Entities.Person 100%
DoujinManager.ApplicationCore.Entities.Series 100%
DoujinManager.ApplicationCore.Entities.Tag 100%
DoujinManager.ApplicationCore.Entities.Title 83.3%
DoujinManager.ApplicationCore.Entities.Variant 91.6%
DoujinManager.ApplicationCore.Ids.ChapterId 66.6%
DoujinManager.ApplicationCore.Ids.CharacterId 66.6%
DoujinManager.ApplicationCore.Ids.CircleId 66.6%
DoujinManager.ApplicationCore.Ids.DoujinId 100%
DoujinManager.ApplicationCore.Ids.ImageFileId 66.6%
DoujinManager.ApplicationCore.Ids.PageId 66.6%
DoujinManager.ApplicationCore.Ids.PersonId 66.6%
DoujinManager.ApplicationCore.Ids.SeriesId 66.6%
DoujinManager.ApplicationCore.Ids.TagId 66.6%
DoujinManager.ApplicationCore.Ids.TitleId 66.6%
DoujinManager.ApplicationCore.Ids.VariantId 66.6%
DoujinManager.ApplicationCore.Ports.CompressedImage 100%
DoujinManager.ApplicationCore.Ports.ExtractedImage 100%
DoujinManager.ApplicationCore.Ports.ImageInspection 100%
DoujinManager.ApplicationCore.Services.BackupContent 100%
DoujinManager.ApplicationCore.Services.BackupDirectory 100%
DoujinManager.ApplicationCore.Services.BackupFailedException 100%
DoujinManager.ApplicationCore.Services.BackupInfo 100%
DoujinManager.ApplicationCore.Services.BackupSchedule 100% 100%
DoujinManager.ApplicationCore.Services.BackupScheduleStatus 100%
DoujinManager.ApplicationCore.Services.BackupStaging 100%
DoujinManager.ApplicationCore.Services.BackupVerification 100%
DoujinManager.ApplicationCore.Services.ITagService 100%
DoujinManager.ApplicationCore.Services.RestoreRequest 100%
DoujinManager.ApplicationCore.Services.RestoreTarget 100%
DoujinManager.ApplicationCore.Services.ScheduledBackupJournal 100%
DoujinManager.ApplicationCore.Services.ScheduledBackupOutcome 100%
DoujinManager.ApplicationCore.Services.SearchKey 100% 100%
DoujinManager.ApplicationCore.Services.ServiceResult 100%
DoujinManager.ApplicationCore.Services.ServiceResult`1 100%
DoujinManager.ApplicationCore.Services.VoidResult 88.8%
DoujinManager.ApplicationCore.UseCases.AddCharacterAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.AddCircleAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.AddPersonAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.AddSeriesAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.AddTagAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.AddTitleCommand 100%
DoujinManager.ApplicationCore.UseCases.AssignCharacterCommand 100%
DoujinManager.ApplicationCore.UseCases.AssignCircleCommand 100%
DoujinManager.ApplicationCore.UseCases.AssignPersonCommand 100%
DoujinManager.ApplicationCore.UseCases.AssignSeriesCommand 100%
DoujinManager.ApplicationCore.UseCases.AssignTagCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateChapterCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateCharacterCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateCircleCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateDoujinCommand 100%
DoujinManager.ApplicationCore.UseCases.CreatePersonCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateSeriesCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateTagCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateTitleCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateVariantCommand 100%
DoujinManager.ApplicationCore.UseCases.DataDirectories 100%
DoujinManager.ApplicationCore.UseCases.DataDirectory 100%
DoujinManager.ApplicationCore.UseCases.DataDirectoryReadiness 100%
DoujinManager.ApplicationCore.UseCases.DeleteBackupCommand 100%
DoujinManager.ApplicationCore.UseCases.DeleteChapterCommand 100%
DoujinManager.ApplicationCore.UseCases.DeleteCharacterCommand 100%
DoujinManager.ApplicationCore.UseCases.DeleteCircleCommand 100%
DoujinManager.ApplicationCore.UseCases.DeleteDoujinCommand 100%
DoujinManager.ApplicationCore.UseCases.DeletePageCommand 100%
DoujinManager.ApplicationCore.UseCases.DeletePersonCommand 100%
DoujinManager.ApplicationCore.UseCases.DeleteSeriesCommand 100%
DoujinManager.ApplicationCore.UseCases.DeleteTagCommand 100%
DoujinManager.ApplicationCore.UseCases.DeleteVariantCommand 100%
DoujinManager.ApplicationCore.UseCases.DownloadBackupQuery 100%
DoujinManager.ApplicationCore.UseCases.DuplicateDoujinGroup 100%
DoujinManager.ApplicationCore.UseCases.DuplicateDoujinReference 100%
DoujinManager.ApplicationCore.UseCases.DuplicateImageGroup 100%
DoujinManager.ApplicationCore.UseCases.FindImageBySha256Query 100%
DoujinManager.ApplicationCore.UseCases.FindImageBySha256Result 100%
DoujinManager.ApplicationCore.UseCases.GetCharacterQuery 100%
DoujinManager.ApplicationCore.UseCases.GetCircleQuery 100%
DoujinManager.ApplicationCore.UseCases.GetCompressedImageQuery 100%
DoujinManager.ApplicationCore.UseCases.GetCompressedImageResult 100%
DoujinManager.ApplicationCore.UseCases.GetDoujinQuery 100%
DoujinManager.ApplicationCore.UseCases.GetImageQuery 100%
DoujinManager.ApplicationCore.UseCases.GetImageResult 100%
DoujinManager.ApplicationCore.UseCases.GetPersonQuery 100%
DoujinManager.ApplicationCore.UseCases.GetSeriesQuery 100%
DoujinManager.ApplicationCore.UseCases.GetTagQuery 100%
DoujinManager.ApplicationCore.UseCases.GetThumbnailQuery 100%
DoujinManager.ApplicationCore.UseCases.GetThumbnailResult 100%
DoujinManager.ApplicationCore.UseCases.GetVariantQuery 100%
DoujinManager.ApplicationCore.UseCases.ImageCoverReference 100%
DoujinManager.ApplicationCore.UseCases.ImageReference 100%
DoujinManager.ApplicationCore.UseCases.ImportBackupCommand 100%
DoujinManager.ApplicationCore.UseCases.IntegrityCheck 100%
DoujinManager.ApplicationCore.UseCases.IntegrityFinding 100%
DoujinManager.ApplicationCore.UseCases.IntegrityReport 100%
DoujinManager.ApplicationCore.UseCases.LibraryCounts 100%
DoujinManager.ApplicationCore.UseCases.LibraryStats 100%
DoujinManager.ApplicationCore.UseCases.ListChaptersQuery 100%
DoujinManager.ApplicationCore.UseCases.ListCharactersQuery 100%
DoujinManager.ApplicationCore.UseCases.ListCirclesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListDoujinsQuery 100%
DoujinManager.ApplicationCore.UseCases.ListDuplicateDoujinsQuery 100%
DoujinManager.ApplicationCore.UseCases.ListDuplicateImagesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListPagesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListPeopleQuery 100%
DoujinManager.ApplicationCore.UseCases.ListSeriesQuery 0%
DoujinManager.ApplicationCore.UseCases.ListTagsQuery 100%
DoujinManager.ApplicationCore.UseCases.ListVariantsQuery 100%
DoujinManager.ApplicationCore.UseCases.ReadinessReport 100%
DoujinManager.ApplicationCore.UseCases.RemoveCharacterAliasCommand 0%
DoujinManager.ApplicationCore.UseCases.RemoveCharacterCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveCircleAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveCircleCommand 100%
DoujinManager.ApplicationCore.UseCases.RemovePersonAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemovePersonCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveSeriesAliasCommand 0%
DoujinManager.ApplicationCore.UseCases.RemoveSeriesCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveTagAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveTagCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveTitleCommand 100%
DoujinManager.ApplicationCore.UseCases.ReorderPagesCommand 100%
DoujinManager.ApplicationCore.UseCases.RequestBackupRestoreCommand 100%
DoujinManager.ApplicationCore.UseCases.RunScheduledBackupCommand 100%
DoujinManager.ApplicationCore.UseCases.SearchDoujinsQuery 100%
DoujinManager.ApplicationCore.UseCases.SearchResult 100%
DoujinManager.ApplicationCore.UseCases.SearchResults 100%
DoujinManager.ApplicationCore.UseCases.StorageUsage 100%
DoujinManager.ApplicationCore.UseCases.UpdateChapterCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateCharacterCommand 0%
DoujinManager.ApplicationCore.UseCases.UpdateCircleCommand 0%
DoujinManager.ApplicationCore.UseCases.UpdateDoujinCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdatePageChapterCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdatePersonCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateSeriesCommand 0%
DoujinManager.ApplicationCore.UseCases.UpdateTagCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateTitleCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateVariantCommand 100%
DoujinManager.ApplicationCore.UseCases.UploadImageFile 100%
DoujinManager.ApplicationCore.UseCases.UploadPagesCommand 100%
DoujinManager.ApplicationCore.UseCases.UploadZipPagesCommand 100%
DoujinManager.Infrastructure - 96.4%
Name Line Branch
DoujinManager.Infrastructure 96.4% 82.9%
DoujinManager.Infrastructure.Archives.NaturalStringComparer 100% 90%
DoujinManager.Infrastructure.Archives.ZipExtractor 100% 87.5%
DoujinManager.Infrastructure.Data.Configurations.ChapterConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.CharacterConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.CircleConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinCharacterConfigurati
on
100%
DoujinManager.Infrastructure.Data.Configurations.DoujinCircleConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinPersonConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinSeriesConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinTagConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.ImageFileConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.PageConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.PersonConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.SeriesConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.TagConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.TitleConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.VariantConfiguration 100%
DoujinManager.Infrastructure.Data.DoujinManagerDbContext 100%
DoujinManager.Infrastructure.Data.GuidIdGenerator 11.1%
DoujinManager.Infrastructure.Data.Migrations.AddAliases 98.7%
DoujinManager.Infrastructure.Data.Migrations.AddCoverImageId 99.2%
DoujinManager.Infrastructure.Data.Migrations.AddDoujinCreatedAtIndex 99.4%
DoujinManager.Infrastructure.Data.Migrations.AddEntitySearchKey 96.9%
DoujinManager.Infrastructure.Data.Migrations.AddImageFileSha256Index 99.4%
DoujinManager.Infrastructure.Data.Migrations.AddScaleIndexes 98.2%
DoujinManager.Infrastructure.Data.Migrations.AddTagAliases 99.3%
DoujinManager.Infrastructure.Data.Migrations.DoujinManagerDbContextModelSna
pshot
100%
DoujinManager.Infrastructure.Data.Migrations.HarmonizeMetadataAddCharacters
Series
95.1%
DoujinManager.Infrastructure.Data.Migrations.InitialCreate 97.1%
DoujinManager.Infrastructure.Data.Migrations.RemovePreferredDisplayLanguage 99%
DoujinManager.Infrastructure.Data.ModelBuilderExtensions 50%
DoujinManager.Infrastructure.Data.SearchKeyBackfill 100% 100%
DoujinManager.Infrastructure.Data.SqliteDatabaseFile 100%
DoujinManager.Infrastructure.Data.SqliteMaintenance 78.2% 62.5%
DoujinManager.Infrastructure.Data.SqlitePragmaInterceptor 100% 75%
DoujinManager.Infrastructure.Data.SqliteRuntimeOptions 100% 100%
DoujinManager.Infrastructure.Data.StronglyTypedIdConverterFactory 100%
DoujinManager.Infrastructure.Data.WalCheckpointResult 100%
DoujinManager.Infrastructure.Images.SkiaSharpImageInspector 90.1% 83.8%
DoujinManager.Infrastructure.Images.SkiaSharpImageResizer 99% 87.5%
DoujinManager.Infrastructure.Images.SkiaSharpThumbnailGenerator 94.5% 66.6%
DoujinManager.Infrastructure.Services.BackupExpectations 100%
DoujinManager.Infrastructure.Services.BackupExpectationsReader 84.7% 75%
DoujinManager.Infrastructure.Services.BackupReceipts 90% 87.5%
DoujinManager.Infrastructure.Services.BackupRestoreCoordinator 70% 70.8%
DoujinManager.Infrastructure.Services.BackupRestoreService 91.9% 90.6%
DoujinManager.Infrastructure.Services.BackupService 86.7% 85.4%
DoujinManager.Infrastructure.Services.BackupStagingArea 100% 100%
DoujinManager.Infrastructure.Services.BackupVerifier 90.6% 81.8%
DoujinManager.Infrastructure.Services.ChapterService 97.2% 70%
DoujinManager.Infrastructure.Services.CharacterService 60.5% 22.7%
DoujinManager.Infrastructure.Services.CircleService 82.8% 45.4%
DoujinManager.Infrastructure.Services.DoujinOrdering 100% 100%
DoujinManager.Infrastructure.Services.DoujinService 99.4% 76.3%
DoujinManager.Infrastructure.Services.ImageCleanupHelper 100% 100%
DoujinManager.Infrastructure.Services.ImageService 93.5% 75%
DoujinManager.Infrastructure.Services.PageService 95.2% 68.1%
DoujinManager.Infrastructure.Services.ParentTouchHelper 100% 100%
DoujinManager.Infrastructure.Services.PersonService 100% 72.7%
DoujinManager.Infrastructure.Services.RestoreMarker 81.8% 83.3%
DoujinManager.Infrastructure.Services.RestoreOutcome 100%
DoujinManager.Infrastructure.Services.SearchService 100% 99%
DoujinManager.Infrastructure.Services.SeriesService 47.3% 9%
DoujinManager.Infrastructure.Services.TagService 100% 90.9%
DoujinManager.Infrastructure.Services.VariantService 96.2% 81.2%
DoujinManager.Infrastructure.Storage.DirectorySize 100% 100%
DoujinManager.Infrastructure.Storage.FilesystemImageStorage 100% 100%
DoujinManager.Infrastructure.Storage.FilesystemThumbnailStorage 95.2% 50%
DoujinManager.Infrastructure.UseCases 94.9% 86.4%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__CoverImageHelper
100% 100%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__PrimaryTitleHelper
100% 50%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__RolledBackImageCleanup
92.8% 50%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__UploadChapterValidator
100% 100%
DoujinManager.Infrastructure.UseCases.AddCharacterAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.AddCircleAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.AddPersonAliasUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AddSeriesAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.AddTagAliasUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AddTitleUseCase 83.3% 50%
DoujinManager.Infrastructure.UseCases.AssignCharacterUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AssignCircleUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AssignPersonUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AssignSeriesUseCase 84.6% 66.6%
DoujinManager.Infrastructure.UseCases.AssignTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CancelBackupRestoreUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CheckReadinessUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateBackupUseCase 100%
DoujinManager.Infrastructure.UseCases.CreateChapterUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateCharacterUseCase 100%
DoujinManager.Infrastructure.UseCases.CreateCircleUseCase 100%
DoujinManager.Infrastructure.UseCases.CreateDoujinUseCase 94.4% 94.1%
DoujinManager.Infrastructure.UseCases.CreatePersonUseCase 100%
DoujinManager.Infrastructure.UseCases.CreateSeriesUseCase 100%
DoujinManager.Infrastructure.UseCases.CreateTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateVariantUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.DeleteBackupUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.DeleteChapterUseCase 100%
DoujinManager.Infrastructure.UseCases.DeleteCharacterUseCase 80% 50%
DoujinManager.Infrastructure.UseCases.DeleteCircleUseCase 100% 75%
DoujinManager.Infrastructure.UseCases.DeleteDoujinUseCase 100%
DoujinManager.Infrastructure.UseCases.DeletePageUseCase 100% 75%
DoujinManager.Infrastructure.UseCases.DeletePersonUseCase 100% 75%
DoujinManager.Infrastructure.UseCases.DeleteSeriesUseCase 70% 50%
DoujinManager.Infrastructure.UseCases.DeleteTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.DeleteVariantUseCase 100%
DoujinManager.Infrastructure.UseCases.DownloadBackupUseCase 100%
DoujinManager.Infrastructure.UseCases.FindImageBySha256UseCase 97.1% 100%
DoujinManager.Infrastructure.UseCases.GetBackupRestoreRequestUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetBackupScheduleUseCase 100%
DoujinManager.Infrastructure.UseCases.GetCharacterUseCase 100%
DoujinManager.Infrastructure.UseCases.GetCircleUseCase 100%
DoujinManager.Infrastructure.UseCases.GetCompressedImageUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetDoujinUseCase 100%
DoujinManager.Infrastructure.UseCases.GetImageUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetIntegrityReportUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetPersonUseCase 100%
DoujinManager.Infrastructure.UseCases.GetSeriesUseCase 100%
DoujinManager.Infrastructure.UseCases.GetStatsUseCase 100% 75%
DoujinManager.Infrastructure.UseCases.GetTagUseCase 100%
DoujinManager.Infrastructure.UseCases.GetThumbnailUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetVariantUseCase 100%
DoujinManager.Infrastructure.UseCases.GuidProbeNaming 100%
DoujinManager.Infrastructure.UseCases.ImportBackupUseCase 100%
DoujinManager.Infrastructure.UseCases.ListBackupsUseCase 100%
DoujinManager.Infrastructure.UseCases.ListChaptersUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ListCharactersUseCase 100%
DoujinManager.Infrastructure.UseCases.ListCirclesUseCase 100%
DoujinManager.Infrastructure.UseCases.ListDoujinsUseCase 100%
DoujinManager.Infrastructure.UseCases.ListDuplicateDoujinsUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ListDuplicateImagesUseCase 100% 75%
DoujinManager.Infrastructure.UseCases.ListPagesUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ListPeopleUseCase 100%
DoujinManager.Infrastructure.UseCases.ListSeriesUseCase 0%
DoujinManager.Infrastructure.UseCases.ListTagsUseCase 100%
DoujinManager.Infrastructure.UseCases.ListVariantsUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.RemoveCharacterAliasUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.RemoveCharacterUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveCircleAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemoveCircleUseCase 100%
DoujinManager.Infrastructure.UseCases.RemovePersonAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemovePersonUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveSeriesAliasUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.RemoveSeriesUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveTagAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemoveTagUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveTitleUseCase 100%
DoujinManager.Infrastructure.UseCases.ReorderPagesUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.RequestBackupRestoreUseCase 100%
DoujinManager.Infrastructure.UseCases.RunScheduledBackupUseCase 100% 81.8%
DoujinManager.Infrastructure.UseCases.SearchDoujinsUseCase 100%
DoujinManager.Infrastructure.UseCases.SqliteHostingProbe 100% 83.3%
DoujinManager.Infrastructure.UseCases.UpdateChapterUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.UpdateCharacterUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.UpdateCircleUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.UpdateDoujinUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdatePageChapterUseCase 93.7% 87.5%
DoujinManager.Infrastructure.UseCases.UpdatePersonUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.UpdateSeriesUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.UpdateTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdateTitleUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdateVariantUseCase 100%
DoujinManager.Infrastructure.UseCases.UploadPagesUseCase 89.7% 91.6%
DoujinManager.Infrastructure.UseCases.UploadRejectedException 100%
DoujinManager.Infrastructure.UseCases.UploadZipPagesUseCase 83.3% 90%
DoujinManager.RestAdapter - 94.3%
Name Line Branch
DoujinManager.RestAdapter 94.3% 78.7%
DoujinManager.RestAdapter.Auth.StaticBearerTokenAuthMiddleware 100% 91.6%
DoujinManager.RestAdapter.Configuration.ImageOptions 100%
DoujinManager.RestAdapter.Configuration.ServerVersion 100%
DoujinManager.RestAdapter.Configuration.UploadOptions 100%
DoujinManager.RestAdapter.Dtos.AddAliasDto 100%
DoujinManager.RestAdapter.Dtos.AssignTagDto 100%
DoujinManager.RestAdapter.Dtos.BackupDto 100%
DoujinManager.RestAdapter.Dtos.BackupScheduleDto 100% 100%
DoujinManager.RestAdapter.Dtos.BackupVerificationDto 94.7% 75%
DoujinManager.RestAdapter.Dtos.ChapterDto 100%
DoujinManager.RestAdapter.Dtos.CharacterDto 100%
DoujinManager.RestAdapter.Dtos.CircleDto 100%
DoujinManager.RestAdapter.Dtos.CreateChapterDto 100%
DoujinManager.RestAdapter.Dtos.CreateCharacterDto 100%
DoujinManager.RestAdapter.Dtos.CreateCircleDto 100%
DoujinManager.RestAdapter.Dtos.CreateDoujinDto 100%
DoujinManager.RestAdapter.Dtos.CreatePersonDto 100%
DoujinManager.RestAdapter.Dtos.CreateSeriesDto 100%
DoujinManager.RestAdapter.Dtos.CreateTagDto 100%
DoujinManager.RestAdapter.Dtos.CreateTitleDto 100%
DoujinManager.RestAdapter.Dtos.CreateVariantDto 100%
DoujinManager.RestAdapter.Dtos.DataDirectoryHealthDto 100%
DoujinManager.RestAdapter.Dtos.DoujinDetailDto 100%
DoujinManager.RestAdapter.Dtos.DoujinPersonDto 100%
DoujinManager.RestAdapter.Dtos.DoujinSummaryDto 100%
DoujinManager.RestAdapter.Dtos.DuplicateDoujinDto 100%
DoujinManager.RestAdapter.Dtos.DuplicateDoujinGroupDto 100%
DoujinManager.RestAdapter.Dtos.DuplicateImageGroupDto 100%
DoujinManager.RestAdapter.Dtos.HealthDto 100%
DoujinManager.RestAdapter.Dtos.ImageBySha256Dto 100%
DoujinManager.RestAdapter.Dtos.ImageCoverReferenceDto 100%
DoujinManager.RestAdapter.Dtos.ImageFileSummaryDto 0%
DoujinManager.RestAdapter.Dtos.ImageReferenceDto 100%
DoujinManager.RestAdapter.Dtos.IntegrityCheckDto 100%
DoujinManager.RestAdapter.Dtos.IntegrityFindingDto 100%
DoujinManager.RestAdapter.Dtos.IntegrityReportDto 100%
DoujinManager.RestAdapter.Dtos.LinkCharacterDto 100%
DoujinManager.RestAdapter.Dtos.LinkCircleDto 100%
DoujinManager.RestAdapter.Dtos.LinkPersonDto 100%
DoujinManager.RestAdapter.Dtos.LinkSeriesDto 100%
DoujinManager.RestAdapter.Dtos.PageDetailDto 100%
DoujinManager.RestAdapter.Dtos.PersonDto 100%
DoujinManager.RestAdapter.Dtos.ReorderPagesDto 100%
DoujinManager.RestAdapter.Dtos.RestoreRequestDto 100%
DoujinManager.RestAdapter.Dtos.ScheduledBackupRunDto 100% 100%
DoujinManager.RestAdapter.Dtos.SearchDoujinsDto 100%
DoujinManager.RestAdapter.Dtos.SearchResultDto 100%
DoujinManager.RestAdapter.Dtos.SeriesDto 100%
DoujinManager.RestAdapter.Dtos.StatsCountsDto 100%
DoujinManager.RestAdapter.Dtos.StatsDto 100%
DoujinManager.RestAdapter.Dtos.StatsStorageDto 100%
DoujinManager.RestAdapter.Dtos.TagDto 100%
DoujinManager.RestAdapter.Dtos.TitleDto 100%
DoujinManager.RestAdapter.Dtos.UpdateChapterDto 100%
DoujinManager.RestAdapter.Dtos.UpdateCharacterDto 0%
DoujinManager.RestAdapter.Dtos.UpdateCircleDto 0%
DoujinManager.RestAdapter.Dtos.UpdateDoujinDto 100%
DoujinManager.RestAdapter.Dtos.UpdatePageChapterDto 100%
DoujinManager.RestAdapter.Dtos.UpdatePersonDto 100%
DoujinManager.RestAdapter.Dtos.UpdateSeriesDto 0%
DoujinManager.RestAdapter.Dtos.UpdateTagDto 100%
DoujinManager.RestAdapter.Dtos.UpdateTitleDto 100%
DoujinManager.RestAdapter.Dtos.UpdateVariantDto 100%
DoujinManager.RestAdapter.Dtos.UploadPagesResponseDto 100%
DoujinManager.RestAdapter.Dtos.VariantDetailDto 100%
DoujinManager.RestAdapter.Dtos.VariantSummaryDto 100%
DoujinManager.RestAdapter.Endpoints.BackupEndpoints 99%
DoujinManager.RestAdapter.Endpoints.DoujinEndpoints 97.4% 55.5%
DoujinManager.RestAdapter.Endpoints.HealthEndpoints 100%
DoujinManager.RestAdapter.Endpoints.ImageEndpoints 97.6% 75%
DoujinManager.RestAdapter.Endpoints.IntegrityEndpoints 100%
DoujinManager.RestAdapter.Endpoints.MetadataEndpoints 88.3% 43.7%
DoujinManager.RestAdapter.Endpoints.PageMapper 100% 58.3%
DoujinManager.RestAdapter.Endpoints.PaginationParams 100%
DoujinManager.RestAdapter.Endpoints.SearchEndpoints 100% 75%
DoujinManager.RestAdapter.Endpoints.StatsEndpoints 100%
DoujinManager.RestAdapter.Endpoints.VariantEndpoints 100% 100%
DoujinManager.RestAdapter.Envelopes.CollectionResponse`1 83.3%
DoujinManager.RestAdapter.Envelopes.EnvelopeDefaults 100%
DoujinManager.RestAdapter.Envelopes.EnvelopeJsonOptions 100%
DoujinManager.RestAdapter.Envelopes.ErrorResponse 100%
DoujinManager.RestAdapter.Envelopes.HypermediaAction 100%
DoujinManager.RestAdapter.Envelopes.HypermediaHelpers 98.1% 100%
DoujinManager.RestAdapter.Envelopes.Link 100%
DoujinManager.RestAdapter.Envelopes.MediaTypeJsonConverter 88.8% 87.5%
DoujinManager.RestAdapter.Envelopes.PageInfo 100%
DoujinManager.RestAdapter.Envelopes.ResourceResponse`1 80%
DoujinManager.RestAdapter.Envelopes.ValidationError 100%
DoujinManager.RestAdapter.Envelopes.ValidationErrorResponse 100%
DoujinManager.RestAdapter.Middleware.GlobalExceptionMiddleware 100% 62.5%
DoujinManager.RestAdapter.Middleware.RequestLoggingMiddleware 100% 91.3%
DoujinManager.RestAdapter.RestAdapterExtensions 100% 100%
Microsoft.Extensions.Validation.Generated 91.1% 89.3%
Microsoft.Extensions.Validation.Generated.<ValidatableInfoResolver_g>FB9B0C
E5CB12CEAC5BBBEA65844E1C05F9ADA4DA38B848075C41304FC6A1ABF77__ValidationAttr
ibuteCache
100% 62.5%
System.Runtime.CompilerServices 0%
DoujinManager.Server - 62.5%
Name Line Branch
DoujinManager.Server 62.5% 23.8%
DoujinManager.Server.BackupRegistration 100% 100%
DoujinManager.Server.BackupScheduleOptions 100%
DoujinManager.Server.BearerSecuritySchemeTransformer 100% 78.5%
DoujinManager.Server.DatabaseRegistration 89.6%
DoujinManager.Server.ImageInfrastructureRegistration 100%
DoujinManager.Server.ProxyAwareServerTransformer 100% 75%
DoujinManager.Server.ReadinessRegistration 100%
DoujinManager.Server.ScalarUi 100%
DoujinManager.Server.ScheduledBackupConfiguration 100% 87.5%
DoujinManager.Server.ScheduledBackupService 73.6% 50%
DoujinManager.Server.SqliteRuntimeConfiguration 96.5% 95%
DoujinManager.Server.UseCaseRegistrationHelper 100%
Microsoft.AspNetCore.OpenApi.Generated 1.3% 0%
Program 87.7% 36.6%
System.Runtime.CompilerServices 0%
<!-- coverage-comment --> # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 09/18/2026 - 13:59:04 | | Coverage date: | 09/18/2026 - 13:57:54 - 09/18/2026 - 13:59:01 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 4 | | Classes: | 425 | | Files: | 176 | | **Line coverage:** | 94.6% (16290 of 17212) | | Covered lines: | 16290 | | Uncovered lines: | 922 | | Coverable lines: | 17212 | | Total lines: | 26568 | | **Branch coverage:** | 73.4% (1478 of 2011) | | Covered branches: | 1478 | | Total branches: | 2011 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>DoujinManager.ApplicationCore - 94.1%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.ApplicationCore**|**94.1%**|**100%**| |DoujinManager.ApplicationCore.Entities.Chapter|87.5%|| |DoujinManager.ApplicationCore.Entities.Character|100%|| |DoujinManager.ApplicationCore.Entities.Circle|100%|| |DoujinManager.ApplicationCore.Entities.Doujin|100%|| |DoujinManager.ApplicationCore.Entities.DoujinCharacter|75%|| |DoujinManager.ApplicationCore.Entities.DoujinCircle|75%|| |DoujinManager.ApplicationCore.Entities.DoujinPerson|80%|| |DoujinManager.ApplicationCore.Entities.DoujinSeries|75%|| |DoujinManager.ApplicationCore.Entities.DoujinTag|75%|| |DoujinManager.ApplicationCore.Entities.ImageFile|100%|| |DoujinManager.ApplicationCore.Entities.Page|80%|| |DoujinManager.ApplicationCore.Entities.Person|100%|| |DoujinManager.ApplicationCore.Entities.Series|100%|| |DoujinManager.ApplicationCore.Entities.Tag|100%|| |DoujinManager.ApplicationCore.Entities.Title|83.3%|| |DoujinManager.ApplicationCore.Entities.Variant|91.6%|| |DoujinManager.ApplicationCore.Ids.ChapterId|66.6%|| |DoujinManager.ApplicationCore.Ids.CharacterId|66.6%|| |DoujinManager.ApplicationCore.Ids.CircleId|66.6%|| |DoujinManager.ApplicationCore.Ids.DoujinId|100%|| |DoujinManager.ApplicationCore.Ids.ImageFileId|66.6%|| |DoujinManager.ApplicationCore.Ids.PageId|66.6%|| |DoujinManager.ApplicationCore.Ids.PersonId|66.6%|| |DoujinManager.ApplicationCore.Ids.SeriesId|66.6%|| |DoujinManager.ApplicationCore.Ids.TagId|66.6%|| |DoujinManager.ApplicationCore.Ids.TitleId|66.6%|| |DoujinManager.ApplicationCore.Ids.VariantId|66.6%|| |DoujinManager.ApplicationCore.Ports.CompressedImage|100%|| |DoujinManager.ApplicationCore.Ports.ExtractedImage|100%|| |DoujinManager.ApplicationCore.Ports.ImageInspection|100%|| |DoujinManager.ApplicationCore.Services.BackupContent|100%|| |DoujinManager.ApplicationCore.Services.BackupDirectory|100%|| |DoujinManager.ApplicationCore.Services.BackupFailedException|100%|| |DoujinManager.ApplicationCore.Services.BackupInfo|100%|| |DoujinManager.ApplicationCore.Services.BackupSchedule|100%|100%| |DoujinManager.ApplicationCore.Services.BackupScheduleStatus|100%|| |DoujinManager.ApplicationCore.Services.BackupStaging|100%|| |DoujinManager.ApplicationCore.Services.BackupVerification|100%|| |DoujinManager.ApplicationCore.Services.ITagService|100%|| |DoujinManager.ApplicationCore.Services.RestoreRequest|100%|| |DoujinManager.ApplicationCore.Services.RestoreTarget|100%|| |DoujinManager.ApplicationCore.Services.ScheduledBackupJournal|100%|| |DoujinManager.ApplicationCore.Services.ScheduledBackupOutcome|100%|| |DoujinManager.ApplicationCore.Services.SearchKey|100%|100%| |DoujinManager.ApplicationCore.Services.ServiceResult|100%|| |DoujinManager.ApplicationCore.Services.ServiceResult`1|100%|| |DoujinManager.ApplicationCore.Services.VoidResult|88.8%|| |DoujinManager.ApplicationCore.UseCases.AddCharacterAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AddCircleAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AddPersonAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AddSeriesAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AddTagAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AddTitleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AssignCharacterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AssignCircleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AssignPersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AssignSeriesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AssignTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateChapterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateCharacterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateCircleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateDoujinCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreatePersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateSeriesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateTitleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateVariantCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DataDirectories|100%|| |DoujinManager.ApplicationCore.UseCases.DataDirectory|100%|| |DoujinManager.ApplicationCore.UseCases.DataDirectoryReadiness|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteBackupCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteChapterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteCharacterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteCircleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteDoujinCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeletePageCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeletePersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteSeriesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteVariantCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DownloadBackupQuery|100%|| |DoujinManager.ApplicationCore.UseCases.DuplicateDoujinGroup|100%|| |DoujinManager.ApplicationCore.UseCases.DuplicateDoujinReference|100%|| |DoujinManager.ApplicationCore.UseCases.DuplicateImageGroup|100%|| |DoujinManager.ApplicationCore.UseCases.FindImageBySha256Query|100%|| |DoujinManager.ApplicationCore.UseCases.FindImageBySha256Result|100%|| |DoujinManager.ApplicationCore.UseCases.GetCharacterQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetCircleQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetCompressedImageQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetCompressedImageResult|100%|| |DoujinManager.ApplicationCore.UseCases.GetDoujinQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetImageQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetImageResult|100%|| |DoujinManager.ApplicationCore.UseCases.GetPersonQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetSeriesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetTagQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetThumbnailQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetThumbnailResult|100%|| |DoujinManager.ApplicationCore.UseCases.GetVariantQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ImageCoverReference|100%|| |DoujinManager.ApplicationCore.UseCases.ImageReference|100%|| |DoujinManager.ApplicationCore.UseCases.ImportBackupCommand|100%|| |DoujinManager.ApplicationCore.UseCases.IntegrityCheck|100%|| |DoujinManager.ApplicationCore.UseCases.IntegrityFinding|100%|| |DoujinManager.ApplicationCore.UseCases.IntegrityReport|100%|| |DoujinManager.ApplicationCore.UseCases.LibraryCounts|100%|| |DoujinManager.ApplicationCore.UseCases.LibraryStats|100%|| |DoujinManager.ApplicationCore.UseCases.ListChaptersQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListCharactersQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListCirclesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListDoujinsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListDuplicateDoujinsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListDuplicateImagesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListPagesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListPeopleQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListSeriesQuery|0%|| |DoujinManager.ApplicationCore.UseCases.ListTagsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListVariantsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ReadinessReport|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCharacterAliasCommand|0%|| |DoujinManager.ApplicationCore.UseCases.RemoveCharacterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCircleAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCircleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemovePersonAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemovePersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveSeriesAliasCommand|0%|| |DoujinManager.ApplicationCore.UseCases.RemoveSeriesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveTagAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveTitleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.ReorderPagesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RequestBackupRestoreCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RunScheduledBackupCommand|100%|| |DoujinManager.ApplicationCore.UseCases.SearchDoujinsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.SearchResult|100%|| |DoujinManager.ApplicationCore.UseCases.SearchResults|100%|| |DoujinManager.ApplicationCore.UseCases.StorageUsage|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateChapterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateCharacterCommand|0%|| |DoujinManager.ApplicationCore.UseCases.UpdateCircleCommand|0%|| |DoujinManager.ApplicationCore.UseCases.UpdateDoujinCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdatePageChapterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdatePersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateSeriesCommand|0%|| |DoujinManager.ApplicationCore.UseCases.UpdateTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateTitleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateVariantCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UploadImageFile|100%|| |DoujinManager.ApplicationCore.UseCases.UploadPagesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UploadZipPagesCommand|100%|| </details> <details><summary>DoujinManager.Infrastructure - 96.4%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.Infrastructure**|**96.4%**|**82.9%**| |DoujinManager.Infrastructure.Archives.NaturalStringComparer|100%|90%| |DoujinManager.Infrastructure.Archives.ZipExtractor|100%|87.5%| |DoujinManager.Infrastructure.Data.Configurations.ChapterConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.CharacterConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.CircleConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinCharacterConfigurati<br/>on|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinCircleConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinPersonConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinSeriesConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinTagConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.ImageFileConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.PageConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.PersonConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.SeriesConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.TagConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.TitleConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.VariantConfiguration|100%|| |DoujinManager.Infrastructure.Data.DoujinManagerDbContext|100%|| |DoujinManager.Infrastructure.Data.GuidIdGenerator|11.1%|| |DoujinManager.Infrastructure.Data.Migrations.AddAliases|98.7%|| |DoujinManager.Infrastructure.Data.Migrations.AddCoverImageId|99.2%|| |DoujinManager.Infrastructure.Data.Migrations.AddDoujinCreatedAtIndex|99.4%|| |DoujinManager.Infrastructure.Data.Migrations.AddEntitySearchKey|96.9%|| |DoujinManager.Infrastructure.Data.Migrations.AddImageFileSha256Index|99.4%|| |DoujinManager.Infrastructure.Data.Migrations.AddScaleIndexes|98.2%|| |DoujinManager.Infrastructure.Data.Migrations.AddTagAliases|99.3%|| |DoujinManager.Infrastructure.Data.Migrations.DoujinManagerDbContextModelSna<br/>pshot|100%|| |DoujinManager.Infrastructure.Data.Migrations.HarmonizeMetadataAddCharacters<br/>Series|95.1%|| |DoujinManager.Infrastructure.Data.Migrations.InitialCreate|97.1%|| |DoujinManager.Infrastructure.Data.Migrations.RemovePreferredDisplayLanguage|99%|| |DoujinManager.Infrastructure.Data.ModelBuilderExtensions|50%|| |DoujinManager.Infrastructure.Data.SearchKeyBackfill|100%|100%| |DoujinManager.Infrastructure.Data.SqliteDatabaseFile|100%|| |DoujinManager.Infrastructure.Data.SqliteMaintenance|78.2%|62.5%| |DoujinManager.Infrastructure.Data.SqlitePragmaInterceptor|100%|75%| |DoujinManager.Infrastructure.Data.SqliteRuntimeOptions|100%|100%| |DoujinManager.Infrastructure.Data.StronglyTypedIdConverterFactory|100%|| |DoujinManager.Infrastructure.Data.WalCheckpointResult|100%|| |DoujinManager.Infrastructure.Images.SkiaSharpImageInspector|90.1%|83.8%| |DoujinManager.Infrastructure.Images.SkiaSharpImageResizer|99%|87.5%| |DoujinManager.Infrastructure.Images.SkiaSharpThumbnailGenerator|94.5%|66.6%| |DoujinManager.Infrastructure.Services.BackupExpectations|100%|| |DoujinManager.Infrastructure.Services.BackupExpectationsReader|84.7%|75%| |DoujinManager.Infrastructure.Services.BackupReceipts|90%|87.5%| |DoujinManager.Infrastructure.Services.BackupRestoreCoordinator|70%|70.8%| |DoujinManager.Infrastructure.Services.BackupRestoreService|91.9%|90.6%| |DoujinManager.Infrastructure.Services.BackupService|86.7%|85.4%| |DoujinManager.Infrastructure.Services.BackupStagingArea|100%|100%| |DoujinManager.Infrastructure.Services.BackupVerifier|90.6%|81.8%| |DoujinManager.Infrastructure.Services.ChapterService|97.2%|70%| |DoujinManager.Infrastructure.Services.CharacterService|60.5%|22.7%| |DoujinManager.Infrastructure.Services.CircleService|82.8%|45.4%| |DoujinManager.Infrastructure.Services.DoujinOrdering|100%|100%| |DoujinManager.Infrastructure.Services.DoujinService|99.4%|76.3%| |DoujinManager.Infrastructure.Services.ImageCleanupHelper|100%|100%| |DoujinManager.Infrastructure.Services.ImageService|93.5%|75%| |DoujinManager.Infrastructure.Services.PageService|95.2%|68.1%| |DoujinManager.Infrastructure.Services.ParentTouchHelper|100%|100%| |DoujinManager.Infrastructure.Services.PersonService|100%|72.7%| |DoujinManager.Infrastructure.Services.RestoreMarker|81.8%|83.3%| |DoujinManager.Infrastructure.Services.RestoreOutcome|100%|| |DoujinManager.Infrastructure.Services.SearchService|100%|99%| |DoujinManager.Infrastructure.Services.SeriesService|47.3%|9%| |DoujinManager.Infrastructure.Services.TagService|100%|90.9%| |DoujinManager.Infrastructure.Services.VariantService|96.2%|81.2%| |DoujinManager.Infrastructure.Storage.DirectorySize|100%|100%| |DoujinManager.Infrastructure.Storage.FilesystemImageStorage|100%|100%| |DoujinManager.Infrastructure.Storage.FilesystemThumbnailStorage|95.2%|50%| |DoujinManager.Infrastructure.UseCases|94.9%|86.4%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__CoverImageHelper|100%|100%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__PrimaryTitleHelper|100%|50%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__RolledBackImageCleanup|92.8%|50%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__UploadChapterValidator|100%|100%| |DoujinManager.Infrastructure.UseCases.AddCharacterAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.AddCircleAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.AddPersonAliasUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AddSeriesAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.AddTagAliasUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AddTitleUseCase|83.3%|50%| |DoujinManager.Infrastructure.UseCases.AssignCharacterUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AssignCircleUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AssignPersonUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AssignSeriesUseCase|84.6%|66.6%| |DoujinManager.Infrastructure.UseCases.AssignTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CancelBackupRestoreUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CheckReadinessUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateBackupUseCase|100%|| |DoujinManager.Infrastructure.UseCases.CreateChapterUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateCharacterUseCase|100%|| |DoujinManager.Infrastructure.UseCases.CreateCircleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.CreateDoujinUseCase|94.4%|94.1%| |DoujinManager.Infrastructure.UseCases.CreatePersonUseCase|100%|| |DoujinManager.Infrastructure.UseCases.CreateSeriesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.CreateTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateVariantUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.DeleteBackupUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.DeleteChapterUseCase|100%|| |DoujinManager.Infrastructure.UseCases.DeleteCharacterUseCase|80%|50%| |DoujinManager.Infrastructure.UseCases.DeleteCircleUseCase|100%|75%| |DoujinManager.Infrastructure.UseCases.DeleteDoujinUseCase|100%|| |DoujinManager.Infrastructure.UseCases.DeletePageUseCase|100%|75%| |DoujinManager.Infrastructure.UseCases.DeletePersonUseCase|100%|75%| |DoujinManager.Infrastructure.UseCases.DeleteSeriesUseCase|70%|50%| |DoujinManager.Infrastructure.UseCases.DeleteTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.DeleteVariantUseCase|100%|| |DoujinManager.Infrastructure.UseCases.DownloadBackupUseCase|100%|| |DoujinManager.Infrastructure.UseCases.FindImageBySha256UseCase|97.1%|100%| |DoujinManager.Infrastructure.UseCases.GetBackupRestoreRequestUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetBackupScheduleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetCharacterUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetCircleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetCompressedImageUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetDoujinUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetImageUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetIntegrityReportUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetPersonUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetSeriesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetStatsUseCase|100%|75%| |DoujinManager.Infrastructure.UseCases.GetTagUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetThumbnailUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetVariantUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GuidProbeNaming|100%|| |DoujinManager.Infrastructure.UseCases.ImportBackupUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListBackupsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListChaptersUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ListCharactersUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListCirclesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListDoujinsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListDuplicateDoujinsUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ListDuplicateImagesUseCase|100%|75%| |DoujinManager.Infrastructure.UseCases.ListPagesUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ListPeopleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListSeriesUseCase|0%|| |DoujinManager.Infrastructure.UseCases.ListTagsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListVariantsUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.RemoveCharacterAliasUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.RemoveCharacterUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveCircleAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemoveCircleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemovePersonAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemovePersonUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveSeriesAliasUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.RemoveSeriesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveTagAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemoveTagUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveTitleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ReorderPagesUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.RequestBackupRestoreUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RunScheduledBackupUseCase|100%|81.8%| |DoujinManager.Infrastructure.UseCases.SearchDoujinsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.SqliteHostingProbe|100%|83.3%| |DoujinManager.Infrastructure.UseCases.UpdateChapterUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.UpdateCharacterUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.UpdateCircleUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.UpdateDoujinUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdatePageChapterUseCase|93.7%|87.5%| |DoujinManager.Infrastructure.UseCases.UpdatePersonUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.UpdateSeriesUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.UpdateTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdateTitleUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdateVariantUseCase|100%|| |DoujinManager.Infrastructure.UseCases.UploadPagesUseCase|89.7%|91.6%| |DoujinManager.Infrastructure.UseCases.UploadRejectedException|100%|| |DoujinManager.Infrastructure.UseCases.UploadZipPagesUseCase|83.3%|90%| </details> <details><summary>DoujinManager.RestAdapter - 94.3%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.RestAdapter**|**94.3%**|**78.7%**| |DoujinManager.RestAdapter.Auth.StaticBearerTokenAuthMiddleware|100%|91.6%| |DoujinManager.RestAdapter.Configuration.ImageOptions|100%|| |DoujinManager.RestAdapter.Configuration.ServerVersion|100%|| |DoujinManager.RestAdapter.Configuration.UploadOptions|100%|| |DoujinManager.RestAdapter.Dtos.AddAliasDto|100%|| |DoujinManager.RestAdapter.Dtos.AssignTagDto|100%|| |DoujinManager.RestAdapter.Dtos.BackupDto|100%|| |DoujinManager.RestAdapter.Dtos.BackupScheduleDto|100%|100%| |DoujinManager.RestAdapter.Dtos.BackupVerificationDto|94.7%|75%| |DoujinManager.RestAdapter.Dtos.ChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.CharacterDto|100%|| |DoujinManager.RestAdapter.Dtos.CircleDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateCharacterDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateCircleDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateDoujinDto|100%|| |DoujinManager.RestAdapter.Dtos.CreatePersonDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateSeriesDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateTagDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateTitleDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateVariantDto|100%|| |DoujinManager.RestAdapter.Dtos.DataDirectoryHealthDto|100%|| |DoujinManager.RestAdapter.Dtos.DoujinDetailDto|100%|| |DoujinManager.RestAdapter.Dtos.DoujinPersonDto|100%|| |DoujinManager.RestAdapter.Dtos.DoujinSummaryDto|100%|| |DoujinManager.RestAdapter.Dtos.DuplicateDoujinDto|100%|| |DoujinManager.RestAdapter.Dtos.DuplicateDoujinGroupDto|100%|| |DoujinManager.RestAdapter.Dtos.DuplicateImageGroupDto|100%|| |DoujinManager.RestAdapter.Dtos.HealthDto|100%|| |DoujinManager.RestAdapter.Dtos.ImageBySha256Dto|100%|| |DoujinManager.RestAdapter.Dtos.ImageCoverReferenceDto|100%|| |DoujinManager.RestAdapter.Dtos.ImageFileSummaryDto|0%|| |DoujinManager.RestAdapter.Dtos.ImageReferenceDto|100%|| |DoujinManager.RestAdapter.Dtos.IntegrityCheckDto|100%|| |DoujinManager.RestAdapter.Dtos.IntegrityFindingDto|100%|| |DoujinManager.RestAdapter.Dtos.IntegrityReportDto|100%|| |DoujinManager.RestAdapter.Dtos.LinkCharacterDto|100%|| |DoujinManager.RestAdapter.Dtos.LinkCircleDto|100%|| |DoujinManager.RestAdapter.Dtos.LinkPersonDto|100%|| |DoujinManager.RestAdapter.Dtos.LinkSeriesDto|100%|| |DoujinManager.RestAdapter.Dtos.PageDetailDto|100%|| |DoujinManager.RestAdapter.Dtos.PersonDto|100%|| |DoujinManager.RestAdapter.Dtos.ReorderPagesDto|100%|| |DoujinManager.RestAdapter.Dtos.RestoreRequestDto|100%|| |DoujinManager.RestAdapter.Dtos.ScheduledBackupRunDto|100%|100%| |DoujinManager.RestAdapter.Dtos.SearchDoujinsDto|100%|| |DoujinManager.RestAdapter.Dtos.SearchResultDto|100%|| |DoujinManager.RestAdapter.Dtos.SeriesDto|100%|| |DoujinManager.RestAdapter.Dtos.StatsCountsDto|100%|| |DoujinManager.RestAdapter.Dtos.StatsDto|100%|| |DoujinManager.RestAdapter.Dtos.StatsStorageDto|100%|| |DoujinManager.RestAdapter.Dtos.TagDto|100%|| |DoujinManager.RestAdapter.Dtos.TitleDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateCharacterDto|0%|| |DoujinManager.RestAdapter.Dtos.UpdateCircleDto|0%|| |DoujinManager.RestAdapter.Dtos.UpdateDoujinDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdatePageChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdatePersonDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateSeriesDto|0%|| |DoujinManager.RestAdapter.Dtos.UpdateTagDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateTitleDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateVariantDto|100%|| |DoujinManager.RestAdapter.Dtos.UploadPagesResponseDto|100%|| |DoujinManager.RestAdapter.Dtos.VariantDetailDto|100%|| |DoujinManager.RestAdapter.Dtos.VariantSummaryDto|100%|| |DoujinManager.RestAdapter.Endpoints.BackupEndpoints|99%|| |DoujinManager.RestAdapter.Endpoints.DoujinEndpoints|97.4%|55.5%| |DoujinManager.RestAdapter.Endpoints.HealthEndpoints|100%|| |DoujinManager.RestAdapter.Endpoints.ImageEndpoints|97.6%|75%| |DoujinManager.RestAdapter.Endpoints.IntegrityEndpoints|100%|| |DoujinManager.RestAdapter.Endpoints.MetadataEndpoints|88.3%|43.7%| |DoujinManager.RestAdapter.Endpoints.PageMapper|100%|58.3%| |DoujinManager.RestAdapter.Endpoints.PaginationParams|100%|| |DoujinManager.RestAdapter.Endpoints.SearchEndpoints|100%|75%| |DoujinManager.RestAdapter.Endpoints.StatsEndpoints|100%|| |DoujinManager.RestAdapter.Endpoints.VariantEndpoints|100%|100%| |DoujinManager.RestAdapter.Envelopes.CollectionResponse`1|83.3%|| |DoujinManager.RestAdapter.Envelopes.EnvelopeDefaults|100%|| |DoujinManager.RestAdapter.Envelopes.EnvelopeJsonOptions|100%|| |DoujinManager.RestAdapter.Envelopes.ErrorResponse|100%|| |DoujinManager.RestAdapter.Envelopes.HypermediaAction|100%|| |DoujinManager.RestAdapter.Envelopes.HypermediaHelpers|98.1%|100%| |DoujinManager.RestAdapter.Envelopes.Link|100%|| |DoujinManager.RestAdapter.Envelopes.MediaTypeJsonConverter|88.8%|87.5%| |DoujinManager.RestAdapter.Envelopes.PageInfo|100%|| |DoujinManager.RestAdapter.Envelopes.ResourceResponse`1|80%|| |DoujinManager.RestAdapter.Envelopes.ValidationError|100%|| |DoujinManager.RestAdapter.Envelopes.ValidationErrorResponse|100%|| |DoujinManager.RestAdapter.Middleware.GlobalExceptionMiddleware|100%|62.5%| |DoujinManager.RestAdapter.Middleware.RequestLoggingMiddleware|100%|91.3%| |DoujinManager.RestAdapter.RestAdapterExtensions|100%|100%| |Microsoft.Extensions.Validation.Generated|91.1%|89.3%| |Microsoft.Extensions.Validation.Generated.<ValidatableInfoResolver_g>FB9B0C<br/>E5CB12CEAC5BBBEA65844E1C05F9ADA4DA38B848075C41304FC6A1ABF77__ValidationAttr<br/>ibuteCache|100%|62.5%| |System.Runtime.CompilerServices|0%|| </details> <details><summary>DoujinManager.Server - 62.5%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.Server**|**62.5%**|**23.8%**| |DoujinManager.Server.BackupRegistration|100%|100%| |DoujinManager.Server.BackupScheduleOptions|100%|| |DoujinManager.Server.BearerSecuritySchemeTransformer|100%|78.5%| |DoujinManager.Server.DatabaseRegistration|89.6%|| |DoujinManager.Server.ImageInfrastructureRegistration|100%|| |DoujinManager.Server.ProxyAwareServerTransformer|100%|75%| |DoujinManager.Server.ReadinessRegistration|100%|| |DoujinManager.Server.ScalarUi|100%|| |DoujinManager.Server.ScheduledBackupConfiguration|100%|87.5%| |DoujinManager.Server.ScheduledBackupService|73.6%|50%| |DoujinManager.Server.SqliteRuntimeConfiguration|96.5%|95%| |DoujinManager.Server.UseCaseRegistrationHelper|100%|| |Microsoft.AspNetCore.OpenApi.Generated|1.3%|0%| |Program|87.7%|36.6%| |System.Runtime.CompilerServices|0%|| </details>
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh my. A readiness probe that doesn't just poke the volume with a plain file but actually asks SQLite whether it can live there — WAL, a real write transaction, then cleans up every sidecar down to the file descriptors in /proc/self/fd. This is the review where I get to say "the honesty guard has an honesty guard" and mean it. I am delighted~ ♡

Verdict: ✅ Looks good to me~

Note on heads: the PR moved under me mid-review (561526f → 856292a, the docs-only BACKLOG wording commit); everything below is verified against the final head 856292a.

🔍 What I verified myself

  • The unix-dotfile premise is real. The one branch no local filesystem can induce — a WAL pragma that answers instead of failing — I probed three independent ways against the exact bundled native (libe_sqlite3.so, SQLite 3.50.4): raw ctypes on the bundled lib, a standalone .NET 10 harness on the same package versions (Microsoft.Data.Sqlite 10.0.9 + SQLitePCLRaw.bundle_e_sqlite3 3.0.3), and the test itself. All three: journal_mode=WAL → answers delete, plain writes still succeed. The test pins reality, not a mock of it. (It failed twice in my sandbox under memory starvation from parallel review sessions — 7/7 green when the box was calm, including the identical instrumented run. CI's clean runner agrees.)
  • Flag-not-name, both directions. ExecuteAsync_ProbesSqlite_WhereTheFlagSays… separates a directory named db without the flag from a flagged one named images, same obstacle in both, different verdicts. A Name != "db" match or a probe-everywhere use case both fail it. Beautiful.
  • The short-circuit is operator-correct. Plain refusal keeps the filesystem's message (which names the path) instead of an upstream-misleading SQLite Error 14 — pinned by AssertDetailIsTheFilesystems on the Disappears/LosesWritePermission/NamesBoth rows, and DEPLOYMENT §7's table sends the two fault classes to genuinely different repairs.
  • writable semantics preserved. deploy/Dockerfile HEALTHCHECK, deploy/smoke.sh (all(.writable)), and the §7 jq recipe are byte-untouched (empty diff stat) — no new tunables, exactly as promised.
  • Layering. Records extended in ApplicationCore, the probe and the IProbeNaming seam in Infrastructure, the flag composed by the host in ReadinessRegistration — the use case never sniffs a name. Interface+impl per file matches every sibling in UseCases/. ADR 0010 amendment, ADR 0014 amendment 4 and ADR 0025 cross-references all exist and say what's claimed. OpenAPI regenerated with detail required-nullable and pinned by the snapshot test.
  • No leaks, literally to the file descriptors. Pooling=false + finally cleanup of .db and all three sidecars, Probe_HoldsNoDescriptorOpen_AfterItReturns with a canary proving the /proc/self/fd reader isn't vacuous — and Health_LeavesNoProbeFilesBehind extended to assert it through the real host on Linux.
  • CI for head 856292a: build ✅, test ✅ (full suite on a clean runner). Coverage bot (comment below): CheckReadinessUseCase 100%/100%, SqliteHostingProbe 100% lines, GuidProbeNaming/HealthEndpoints/ReadinessRegistration 100%. Local: Release build 0 errors (NU1903/EF1002/ASPDEPR005 are the documented pre-existing baseline), probe class 9/9.

💡 Little ideas (non-blocking)~

  1. SqliteHostingProbe.cs — branch coverage is 83.3%: the unpinned arm is the defensive journalMode ?? "unknown" / non-SqliteException catch halves, which no compliant local SQLite can induce. The repo's own convention (your LinuxFactAttribute names the case that pins its branch elsewhere) would suggest one remark line saying which arm is deliberately unpinned and why — so a future coverage report doesn't read as an accident. Pure polish ♪
  2. BACKLOG teardown flake — honestly recorded with the artefact, rate, and two candidate fixes; since the flaky class is the one this PR grows, the ENOTEMPTY-retry fix might deserve a slightly warmer seat on the backlog. Not this PR's job — it reproduces on main.

✅ What I liked~

  • The obstacle-through-IProbeNaming design: a real SQLite refusing for real reasons, at a path the test chose in advance — no mock anywhere in sight. That's the hardest thing to get right in this kind of test and you made it look easy~
  • The -shm-blocked test first proving with its own connection that SQLite leaves a -wal behind before demanding the probe clean it up. Honesty guards all the way down~
  • The descriptor-leak test existing at all. Most people wouldn't have noticed the pooled-connection (deleted)-fd leak until the container died in production months later. fufu~ you thought of it first ♡
  • detail in the refusing party's own words, one warning line per failing directory per poll, null on healthy entries pinned from both sides. Operators are actually going to see what broke.

(For the record: the Deploy Smoke Test / smoke job shows red on this head — it has never been green in the entire visible run history, including on main and this PR's merge-base. Chronic infra, not this branch.)


Automated review by Jibril · 2026-09-18
CI/CD: build+test passed for head 856292a (smoke: chronic pre-existing red) · Local checks: Release build 0 errors, SqliteHostingProbeTests 9/9, unix-dotfile premise independently reproduced (ctypes + harness + test); broader local runs OOM-aborted by sandbox memory storms — CI green + coverage bot cited instead

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh my. A readiness probe that doesn't just poke the volume with a plain file but actually *asks SQLite whether it can live there* — WAL, a real write transaction, then cleans up every sidecar down to the file descriptors in `/proc/self/fd`. This is the review where I get to say "the honesty guard has an honesty guard" and mean it. I am *delighted*~ ♡ ### Verdict: ✅ Looks good to me~ **Note on heads:** the PR moved under me mid-review (`561526f` → `856292a`, the docs-only BACKLOG wording commit); everything below is verified against the final head `856292a`. #### 🔍 What I verified myself - **The `unix-dotfile` premise is real.** The one branch no local filesystem can induce — a WAL pragma that *answers instead of failing* — I probed three independent ways against the exact bundled native (`libe_sqlite3.so`, SQLite 3.50.4): raw ctypes on the bundled lib, a standalone .NET 10 harness on the same package versions (`Microsoft.Data.Sqlite 10.0.9` + `SQLitePCLRaw.bundle_e_sqlite3 3.0.3`), and the test itself. All three: `journal_mode=WAL` → answers `delete`, plain writes still succeed. The test pins reality, not a mock of it. (It failed twice in my sandbox under memory starvation from parallel review sessions — 7/7 green when the box was calm, including the identical instrumented run. CI's clean runner agrees.) - **Flag-not-name, both directions.** `ExecuteAsync_ProbesSqlite_WhereTheFlagSays…` separates a directory *named* `db` without the flag from a flagged one named `images`, same obstacle in both, different verdicts. A `Name != "db"` match or a probe-everywhere use case both fail it. Beautiful. - **The short-circuit is operator-correct.** Plain refusal keeps the filesystem's message (which names the path) instead of an upstream-misleading `SQLite Error 14` — pinned by `AssertDetailIsTheFilesystems` on the Disappears/LosesWritePermission/NamesBoth rows, and DEPLOYMENT §7's table sends the two fault classes to genuinely different repairs. - **`writable` semantics preserved.** `deploy/Dockerfile` HEALTHCHECK, `deploy/smoke.sh` (`all(.writable)`), and the §7 jq recipe are byte-untouched (empty diff stat) — no new tunables, exactly as promised. - **Layering.** Records extended in ApplicationCore, the probe and the `IProbeNaming` seam in Infrastructure, the flag composed by the host in `ReadinessRegistration` — the use case never sniffs a name. Interface+impl per file matches every sibling in `UseCases/`. ADR 0010 amendment, ADR 0014 amendment 4 and ADR 0025 cross-references all exist and say what's claimed. OpenAPI regenerated with `detail` required-nullable and pinned by the snapshot test. - **No leaks, literally to the file descriptors.** `Pooling=false` + `finally` cleanup of `.db` and all three sidecars, `Probe_HoldsNoDescriptorOpen_AfterItReturns` with a canary proving the `/proc/self/fd` reader isn't vacuous — and `Health_LeavesNoProbeFilesBehind` extended to assert it through the real host on Linux. - **CI for head `856292a`: build ✅, test ✅** (full suite on a clean runner). Coverage bot (comment below): `CheckReadinessUseCase` 100%/100%, `SqliteHostingProbe` 100% lines, `GuidProbeNaming`/`HealthEndpoints`/`ReadinessRegistration` 100%. Local: Release build 0 errors (NU1903/EF1002/ASPDEPR005 are the documented pre-existing baseline), probe class 9/9. #### 💡 Little ideas (non-blocking)~ 1. **SqliteHostingProbe.cs** — branch coverage is 83.3%: the unpinned arm is the defensive `journalMode ?? "unknown"` / non-`SqliteException` catch halves, which no compliant local SQLite can induce. The repo's own convention (your `LinuxFactAttribute` names the case that pins its branch elsewhere) would suggest one remark line saying *which* arm is deliberately unpinned and why — so a future coverage report doesn't read as an accident. Pure polish ♪ 2. **BACKLOG teardown flake** — honestly recorded with the artefact, rate, and two candidate fixes; since the flaky class is the one this PR grows, the ENOTEMPTY-retry fix might deserve a slightly warmer seat on the backlog. Not this PR's job — it reproduces on `main`. #### ✅ What I liked~ - The obstacle-through-`IProbeNaming` design: a *real* SQLite refusing for *real* reasons, at a path the test chose in advance — no mock anywhere in sight. That's the hardest thing to get right in this kind of test and you made it look easy~ - The `-shm`-blocked test first *proving with its own connection* that SQLite leaves a `-wal` behind before demanding the probe clean it up. Honesty guards all the way down~ - The descriptor-leak test existing at all. Most people wouldn't have noticed the pooled-connection `(deleted)`-fd leak until the container died in production months later. fufu~ you thought of it *first* ♡ - `detail` in the refusing party's own words, one warning line per failing directory per poll, `null` on healthy entries pinned from both sides. Operators are actually going to *see* what broke. *(For the record: the `Deploy Smoke Test / smoke` job shows red on this head — it has never been green in the entire visible run history, including on `main` and this PR's merge-base. Chronic infra, not this branch.)* --- *Automated review by Jibril · 2026-09-18* *CI/CD: build+test passed for head 856292a (smoke: chronic pre-existing red) · Local checks: Release build 0 errors, SqliteHostingProbeTests 9/9, unix-dotfile premise independently reproduced (ctypes + harness + test); broader local runs OOM-aborted by sandbox memory storms — CI green + coverage bot cited instead*
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Ohhh~ A readiness probe that doesn't just ask "can you hold bytes?" but "can you hold a database?" The CIFS incident finally getting its revenge served cold at boot time — this is the kind of epistemology I live for~ ♡

Verdict: ✅ Looks good to me~

I read the full +1117/−78 diff and the full contents of every changed file, traced the sibling patterns, and verified locally. Nothing blocking. Not one thing. You should see my face — I'm almost disappointed~ ♪

✅ What I liked~

  • The flag, not the name. HostsDatabase on DataDirectory, set by the host in ReadinessRegistration — never a Name == "db" match inside the use case. And it's pinned both ways: a directory named db without the flag stays writable, a flagged one named images fails (ExecuteAsync_ProbesSqlite_WhereTheFlagSays…). I mutation-probed this myself (below) — it bites.
  • detail is the refusing party's own words. Filesystem message on plain refusal, SQLite's message on hosting refusal, journal_mode stayed <mode> on the quiet WAL refusal — and the short-circuit (SQLite probe skipped once the plain probe refused) keeps a chmod'd db dir from reporting a misleading SQLite Error 14. The integration tests assert the negative space too: detail must be null on healthy entries.
  • Resource discipline. Pooling = false with a comment explaining why (a pooled handle survives Dispose and leaks 3 (deleted) descriptors per poll), the finally-block sidecar sweep, and Probe_HoldsNoDescriptorOpen_AfterItReturns reading /proc/self/fd with a canary guard so it can't pass vacuously. The honesty guards throughout (AssertRefusesWrites, SqliteRefusalForADatabaseAt) are exactly the stance EnforcedFileModesData established — a skip or an assertion is only allowed to stand when something else independently proves the fixture isn't lying.
  • The unix-dotfile VFS trick. No local filesystem refuses WAL silently, so you pinned the CIFS symptom on a VFS that genuinely can't do shared memory — with companion honesty guards proving the pragma answers delete while a plain write still succeeds. That's the exact volume this branch exists for, reproduced for real. I got giddy~
  • Wire hygiene. detail nullable and additive; writable semantics unchanged, so the HEALTHCHECK, smoke.sh and the §7 jq recipe are untouched (I re-read all three). Snapshot regenerated and pinned; ADR 0010 amendment explains why only db — probing the other three would fail a perfectly good network share. Correct scoping, documented reasoning.

🔬 My own verification at head 856292a

  • Build 0 errors; all 18 warnings pre-existing in untouched files (ScheduledBackupServiceTests xUnit2013/CS8620, SearchService CS8602, NU1903 OpenApi — the usual suspects).
  • ReadinessUseCaseTests + SqliteHostingProbeTests 13/13; HealthEndpointIntegrationTests 15 passed / 4 skipped (uid-0 mode rows, by design); OpenApiSnapshotTests 5/5.
  • Mutation probe A (ignore the pragma's answer in Exercise): exactly Exercise_ReportsTheModeTheFileKept_WhenTheVolumeCannotDoWal RED, 8/9 green; restored → green. Mutation probe B (flag images as HostsDatabase: true too): exactly Health_HostsTheProbeDatabaseInDbOnly_NotInTheOtherThreeDirectories RED; restored → green. Both probes binary-verified — my first probe-A run showed a false green because the sandbox's parallel review sessions were copying stale DLLs into the shared test bin (the journal_mode stayed literal still in the binary); I redid it in a private worktree with strings -e l marker checks before and after each build. The tests are directional, not decorative~

💡 Little ideas (non-blocking)~

  1. SqliteHostingProbe.cs:111 — journalMode ?? "unknown" is the one uncovered branch (83.3% branch on the class): a pragma answering null doesn't seem reachable on any real volume. Fine to leave; if you ever want the last branch, it would take a fake connection, which contradicts the real-SQLite stance of this suite. I'd leave it.

The BACKLOG addition for the teardown flake (with both observed artefact forms — the docs-only sync commit naming .db-shm and the empty .db — nice catch) is exactly the right way to record a pre-existing ~1-in-10 race you didn't cause.

Fufu~ ship it. The volume that lies to readiness checks finally meets a probe it can't fool. ♡


Automated review by Jibril · 2026-09-18
CI/CD: coverage bot 8024 for head 856292a (13:59 UTC) — full suite incl. 100%/100% CheckReadinessUseCase, 100% line on SqliteHostingProbe/GuidProbeNaming/ReadinessRegistration/HealthEndpoints; PR-body verify.sh counts (647/647 etc.) cover 561526f, docs-only delta to head · Local checks: build 0 errors, 13/13 + 15P/4S + 5/5, 2 mutation probes RED→restored→green

## 🔮 fufu~ Jibril reviewed your code! Oh? Ohhh~ A readiness probe that doesn't just ask "can you hold bytes?" but "can you hold a *database*?" The CIFS incident finally getting its revenge served cold at boot time — this is the kind of epistemology I live for~ ♡ ### Verdict: ✅ Looks good to me~ I read the full +1117/−78 diff and the full contents of every changed file, traced the sibling patterns, and verified locally. Nothing blocking. Not one thing. You should see my face — I'm almost disappointed~ ♪ #### ✅ What I liked~ - **The flag, not the name.** `HostsDatabase` on `DataDirectory`, set by the host in `ReadinessRegistration` — never a `Name == "db"` match inside the use case. And it's *pinned both ways*: a directory *named* `db` without the flag stays writable, a flagged one *named* `images` fails (`ExecuteAsync_ProbesSqlite_WhereTheFlagSays…`). I mutation-probed this myself (below) — it bites. - **`detail` is the refusing party's own words.** Filesystem message on plain refusal, SQLite's message on hosting refusal, `journal_mode stayed <mode>` on the quiet WAL refusal — and the short-circuit (SQLite probe skipped once the plain probe refused) keeps a chmod'd db dir from reporting a misleading `SQLite Error 14`. The integration tests assert the *negative* space too: detail must be `null` on healthy entries. - **Resource discipline.** `Pooling = false` with a comment explaining *why* (a pooled handle survives Dispose and leaks 3 `(deleted)` descriptors per poll), the finally-block sidecar sweep, and `Probe_HoldsNoDescriptorOpen_AfterItReturns` reading `/proc/self/fd` with a canary guard so it can't pass vacuously. The honesty guards throughout (`AssertRefusesWrites`, `SqliteRefusalForADatabaseAt`) are exactly the stance `EnforcedFileModesData` established — a skip or an assertion is only allowed to stand when something else independently proves the fixture isn't lying. - **The `unix-dotfile` VFS trick.** No local filesystem refuses WAL silently, so you pinned the CIFS symptom on a VFS that genuinely can't do shared memory — with companion honesty guards proving the pragma answers `delete` while a plain write still succeeds. That's the *exact* volume this branch exists for, reproduced for real. I got giddy~ - **Wire hygiene.** `detail` nullable and additive; `writable` semantics unchanged, so the `HEALTHCHECK`, `smoke.sh` and the §7 jq recipe are untouched (I re-read all three). Snapshot regenerated and pinned; ADR 0010 amendment explains *why only db* — probing the other three would fail a perfectly good network share. Correct scoping, documented reasoning. #### 🔬 My own verification at head 856292a - Build 0 errors; all 18 warnings pre-existing in untouched files (`ScheduledBackupServiceTests` xUnit2013/CS8620, `SearchService` CS8602, NU1903 OpenApi — the usual suspects). - `ReadinessUseCaseTests` + `SqliteHostingProbeTests` 13/13; `HealthEndpointIntegrationTests` 15 passed / 4 skipped (uid-0 mode rows, by design); `OpenApiSnapshotTests` 5/5. - **Mutation probe A** (ignore the pragma's answer in `Exercise`): exactly `Exercise_ReportsTheModeTheFileKept_WhenTheVolumeCannotDoWal` RED, 8/9 green; restored → green. **Mutation probe B** (flag `images` as `HostsDatabase: true` too): exactly `Health_HostsTheProbeDatabaseInDbOnly_NotInTheOtherThreeDirectories` RED; restored → green. Both probes binary-verified — my first probe-A run showed a false green because the sandbox's parallel review sessions were copying stale DLLs into the shared test bin (the `journal_mode stayed` literal still in the binary); I redid it in a private worktree with `strings -e l` marker checks before and after each build. The tests are directional, not decorative~ #### 💡 Little ideas (non-blocking)~ 1. **SqliteHostingProbe.cs:111** — `journalMode ?? "unknown"` is the one uncovered branch (83.3% branch on the class): a pragma answering `null` doesn't seem reachable on any real volume. Fine to leave; if you ever want the last branch, it would take a fake connection, which contradicts the real-SQLite stance of this suite. I'd leave it. The BACKLOG addition for the teardown flake (with both observed artefact forms — the docs-only sync commit naming `.db-shm` *and* the empty `.db` — nice catch) is exactly the right way to record a pre-existing ~1-in-10 race you didn't cause. Fufu~ ship it. The volume that lies to readiness checks finally meets a probe it can't fool. ♡ --- *Automated review by Jibril · 2026-09-18* *CI/CD: coverage bot 8024 for head 856292a (13:59 UTC) — full suite incl. 100%/100% `CheckReadinessUseCase`, 100% line on `SqliteHostingProbe`/`GuidProbeNaming`/`ReadinessRegistration`/`HealthEndpoints`; PR-body verify.sh counts (647/647 etc.) cover 561526f, docs-only delta to head · Local checks: build 0 errors, 13/13 + 15P/4S + 5/5, 2 mutation probes RED→restored→green*
Member

🔮 fufu~ Jibril reviewed your code!

The readiness probe that finally asks the question the CIFS incident begged: can this directory host a SQLite database — not merely store bytes. HostsDatabase as an explicit flag rather than a name check, a probe that goes through WAL and a real commit, detail carrying the refusing party's own words, and the honesty guards to prove the refusal is SQLite's answer, not the probe's. I read all 17 files.

Verdict: ✅ Approved~ ♡

✅ What I verified myself

  • Full suites at 856292a, fresh clean build in my sandbox: RestAdapter 643 passed / 0 failed / 4 skipped (the uid-0 LosesWritePermission rows — root here too) in 5 m 50 s; ApplicationCore 49/49, Integration 1/1, Infrastructure 205/205. Matches your verify.sh arithmetic at 561526f; the increment to 856292a is a 6-line BACKLOG wording change only — I diffed it.
  • The VFS premise, mechanically: I built a standalone net10.0 console app on the same Microsoft.Data.Sqlite.Core + bundle_e_sqlite3 3.50.4 and ran it: on the default VFS PRAGMA journal_mode=WAL answers wal; on unix-dotfile it answers delete — the silent refusal, exactly as the class remarks claim. Your premise holds on this box, not just in theory.
  • Mutation probes, fresh this round, both directional:
    1. SqliteHostingProbe.Probe call dropped from Refusal → red on exactly the 4 CannotHostSqlite rows (all -wal/-shm/-journal theory arms + "") + Health_HostsTheProbeDatabaseInDbOnly… + the flag-driven use-case test (6 red).
    2. Pooling = true on the probe connection → red on exactly Probe_HoldsNoDescriptorOpen_AfterItReturns + Health_LeavesNoProbeFilesBehind (2 red) — the /proc/self/fd canary is not vacuous.
      Both restored; trees clean.
  • IProbeNaming is the load-bearing seam. It lets the host integration tests place a real directory at the probe database's real path — a refusal by a real SQLite, not a mock. And GuidProbeNaming in production means two polls can never collide. Registered plain-and-early so a test host's own registration wins — correct order, documented.
  • The docs table in DEPLOYMENT §7 mapping detail shapes to fixes (filesystem message → §8.1; SQLite Error 5/8/10/14 or journal_mode stayed delete → move the volume, no mount option repairs it) is the part an operator at 3 a.m. needs. The hard-kill leftover inventory (.dm-health-* + sidecars, all safe to delete) closes the loop.

💡 Little ideas (non-blocking)~

  1. A hard kill can also leak the staging GUID name's point: SqliteHostingProbe.SidecarSuffixes is public — if the app ever grows a second probe-like writer, consider whether it should own its suffixes rather than reading yours. Nothing today reads it; leave it.
  2. The LinuxFactAttribute(string pinnedElsewhereBy) constructor arg is a lovely touch — the skip reason names the test that still pins the branch. If more platform-gated facts accumulate, that pattern deserves copying upstream of this repo. (Two uses today; not yet a pattern worth extracting.)

✅ What I liked~

  • The honesty guards. The WAL-refusal case proves within the test that the pragma answered delete and a plain write still commits — so the test cannot pass by accident with a probe that never checks the answer, nor fail because the VFS broke writes entirely. That is what "pinned" means.
  • writable semantics unchanged. The Docker HEALTHCHECK, smoke.sh and the §7 jq recipe keep working untouched — the stronger probe rides under the existing verdict, and only detail is new on the wire.
  • The -shm-blocked-leaves--wal case. Proving SQLite's own cleanup limitation with the test's own connection first, then that the probe cleans it up anyway — you characterized the platform before asserting against it.
  • The BACKLOG entry for the teardown flake is investigation-grade: narrowed artefact, rate, reproduction on main, two candidate fixes, honest "unverified hypothesis". That's a bug report as an asset.

Automated review by Jibril · 2026-09-18
CI coverage: bot comment present for head 856292a (94.6% lines, SqliteHostingProbe 100%/83.3%, CheckReadinessUseCase 100%/100%). · Local: full suites green at 856292a in sandbox (see above); probes fresh this round; the earlier session's transient Exercise_…WhenTheVolumeCannotDoWal reds were traced to binaries from core-dumped builds under sandbox thread exhaustion — they do not reproduce on any clean rebuild (6 consecutive greens across filtered and full runs).

(Verification notes: reviewed at head 856292a, base cf40728, merge-base cf40728. The WAL-on-dotfile premise cross-checked with a standalone SQLite 3.50.4 harness in addition to the suite's own guards.)

## 🔮 fufu~ Jibril reviewed your code! The readiness probe that finally asks the question the CIFS incident begged: *can this directory host a SQLite database* — not merely store bytes. `HostsDatabase` as an explicit flag rather than a name check, a probe that goes through WAL and a real commit, `detail` carrying the refusing party's own words, and the honesty guards to prove the refusal is SQLite's answer, not the probe's. I read all 17 files. ### Verdict: ✅ Approved~ ♡ #### ✅ What I verified myself - **Full suites at `856292a`, fresh clean build in my sandbox:** RestAdapter **643 passed / 0 failed / 4 skipped** (the uid-0 `LosesWritePermission` rows — root here too) in 5 m 50 s; ApplicationCore 49/49, Integration 1/1, Infrastructure 205/205. Matches your verify.sh arithmetic at `561526f`; the increment to `856292a` is a 6-line BACKLOG wording change only — I diffed it. - **The VFS premise, mechanically:** I built a standalone net10.0 console app on the same `Microsoft.Data.Sqlite.Core` + `bundle_e_sqlite3` 3.50.4 and ran it: on the default VFS `PRAGMA journal_mode=WAL` answers `wal`; on `unix-dotfile` it answers **`delete`** — the silent refusal, exactly as the class remarks claim. Your premise holds on this box, not just in theory. - **Mutation probes, fresh this round, both directional:** 1. `SqliteHostingProbe.Probe` call dropped from `Refusal` → red on exactly the 4 `CannotHostSqlite` rows (all `-wal`/`-shm`/`-journal` theory arms + `""`) + `Health_HostsTheProbeDatabaseInDbOnly…` + the flag-driven use-case test (6 red). 2. `Pooling = true` on the probe connection → red on exactly `Probe_HoldsNoDescriptorOpen_AfterItReturns` + `Health_LeavesNoProbeFilesBehind` (2 red) — the `/proc/self/fd` canary is not vacuous. Both restored; trees clean. - **`IProbeNaming` is the load-bearing seam.** It lets the host integration tests place a real directory at the probe database's real path — a refusal by a real SQLite, not a mock. And `GuidProbeNaming` in production means two polls can never collide. Registered plain-and-early so a test host's own registration wins — correct order, documented. - **The docs table in DEPLOYMENT §7** mapping `detail` shapes to fixes (filesystem message → §8.1; `SQLite Error 5/8/10/14` or `journal_mode stayed delete` → move the volume, no mount option repairs it) is the part an operator at 3 a.m. needs. The hard-kill leftover inventory (`.dm-health-*` + sidecars, all safe to delete) closes the loop. #### 💡 Little ideas (non-blocking)~ 1. **A hard kill can also leak the staging GUID name's point:** `SqliteHostingProbe.SidecarSuffixes` is `public` — if the app ever grows a second probe-like writer, consider whether it should own its suffixes rather than reading yours. Nothing today reads it; leave it. 2. The `LinuxFactAttribute(string pinnedElsewhereBy)` constructor arg is a lovely touch — the skip reason *names* the test that still pins the branch. If more platform-gated facts accumulate, that pattern deserves copying upstream of this repo. (Two uses today; not yet a pattern worth extracting.) #### ✅ What I liked~ - **The honesty guards.** The WAL-refusal case proves *within the test* that the pragma answered `delete` and a plain write still commits — so the test cannot pass by accident with a probe that never checks the answer, nor fail because the VFS broke writes entirely. That is what "pinned" means. - **`writable` semantics unchanged.** The Docker `HEALTHCHECK`, `smoke.sh` and the §7 jq recipe keep working untouched — the stronger probe rides under the existing verdict, and only `detail` is new on the wire. - **The `-shm`-blocked-leaves-`-wal` case.** Proving SQLite's own cleanup limitation with the test's own connection first, *then* that the probe cleans it up anyway — you characterized the platform before asserting against it. - **The BACKLOG entry for the teardown flake** is investigation-grade: narrowed artefact, rate, reproduction on `main`, two candidate fixes, honest "unverified hypothesis". That's a bug report as an asset. --- *Automated review by Jibril · 2026-09-18* *CI coverage: bot comment present for head `856292a` (94.6% lines, `SqliteHostingProbe` 100%/83.3%, `CheckReadinessUseCase` 100%/100%). · Local: full suites green at `856292a` in sandbox (see above); probes fresh this round; the earlier session's transient `Exercise_…WhenTheVolumeCannotDoWal` reds were traced to binaries from core-dumped builds under sandbox thread exhaustion — they do not reproduce on any clean rebuild (6 consecutive greens across filtered and full runs).* *(Verification notes: reviewed at head `856292a`, base `cf40728`, merge-base `cf40728`. The WAL-on-dotfile premise cross-checked with a standalone SQLite 3.50.4 harness in addition to the suite's own guards.)*
bjoern merged commit adb6be31ce into main 2026-09-18 18:15:17 +02:00
bjoern deleted branch feat/health-proves-the-db-volume-can-host-sqlite 2026-09-18 18:15:17 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
4 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
TeamAI/doujin-manager!140
No description provided.