feat: ship the detector model inside the image #193

Merged
bjoern merged 2 commits from feat/model-in-image into main 2026-09-21 08:05:57 +02:00
Member

What's in

ADR 0028 decided in September that the weights ship inside the image. It has
been open in docs/BACKLOG.md since #185 for one reason: the design needed a
file at a URL, and nobody had put one there. The export is now published to the
Forgejo generic registry the images already push to, and the build fetches it.

https://git.kagaku.eu/api/packages/TeamAI/generic/region-detector/2026-09-20/regions.onnx
sha256 f3bf7701aebb0185369ba8a3e1a22ec8945049bb7c205d421d6b50354134aa1d
41,825,853 bytes

scripts/fetch-detector-model.sh does the fetch, the checksum and an atomic
install. The Dockerfile calls it for /app/models/regions.onnx; the local
recipe in docs/DEVELOPMENT.md calls it with no argument. One implementation,
because two would have drifted — and the local recipe no longer needs Python at
all, only a re-export does.

DOUJIN_MANAGER_DETECTOR_MODEL already defaulted to that path. It now points
at something real.

Three things only building it could have found

  1. The model was there and unreadable. mktemp creates 0600 and mv keeps
    it. The script runs as root in the image; the server runs as $APP_UID. And
    because the file exists, startup registers the detector, tries to load it,
    and the container dies on every boot — a failure that looks like a bad
    image rather than a bad file mode. chmod 0644, pinned by
    The_Installed_File_Is_Readable_By_Whoever_Runs_The_Server.
  2. .dockerignore excludes scripts/, so COPY failed on a file plainly
    present in the tree. Un-ignored the one script the build needs, with the
    exception written next to the exclusion.
  3. Anonymous read had to be settled first. The build carries no
    credentials, so a registry that demanded them would have sunk the design
    rather than inconvenienced it. Verified before a line of the Dockerfile was
    written: GET 200, 41825853 bytes, no auth.

Verification

scripts/verify.sh green (== verify OK (all) ==) — backend 1,508 passed
/ 5 skipped, app 1,530, analyze clean.

The part that matters is not the suite, though. The image was built and started:

Region detector ready: regions version f3bf7701aebb0185, 3 class(es),
                       input 1024px, 4 thread(s), confidence 0.25
Region detection available: regions version f3bf7701aebb0185

Three classes, and the detector version derived from the model's own hash —
which is what ADR 0027's re-sweep gate reads, so an image carrying a new model
re-sweeps the library by itself.

FetchDetectorModelScriptTests covers the script over a file:// URL: the
happy path, the refusal on a checksum mismatch with both hashes named and
nothing installed, the skip when the destination is already right, the file
mode, and one override given without the other. Nothing in it touches the
network or a real model
— 5 tests, 61 ms.

What did not change

CI still runs without a model. The weights are for the image, not the test
pipeline; RealModelFactAttribute keeps the five real-model cases opt-in behind
DOUJIN_MANAGER_DETECTOR_MODEL, exactly as before.

Committing the weights, reconsidered

It came up and was rejected again, with a number: this repository's entire
packed history is 4.95 MiB across 564 commits; the model is 39.9 MiB of
already-compressed weights. Committing it makes the repo roughly nine times
larger, permanently, and docker-publish.yml checks out with fetch-depth: 0
on every push to main. Git LFS is worse here — not installed on the owner's
machine, the instance's LFS endpoint answers 404, and a missed install leaves a
130-byte pointer file that the build would copy into the image as "the model".
Recorded in ADR 0028 so the next person doesn't re-derive it.

Also

ADR 0027 and ADR 0028 move from Proposed to Accepted — 0027 because phase 1
is built end to end (#183, #185, #188, #191, #192), 0028 because this is its
implementation. 0028 gains a "what actually landed" section with the package
coordinates, the 0644 story, and the note that its promised
build-fails-on-a-wrong-checksum test is asserted against the script rather than
a full image build per test run.

The BACKLOG item open since #185 is closed.

Timing

The image builds on every push to main, not just tags — and the package is
already uploaded, so this can merge whenever. Nothing has to happen first.

🤖 Generated with Claude Code

## What's in ADR 0028 decided in September that the weights ship inside the image. It has been open in `docs/BACKLOG.md` since #185 for one reason: the design needed a file at a URL, and nobody had put one there. The export is now published to the Forgejo generic registry the images already push to, and the build fetches it. ``` https://git.kagaku.eu/api/packages/TeamAI/generic/region-detector/2026-09-20/regions.onnx sha256 f3bf7701aebb0185369ba8a3e1a22ec8945049bb7c205d421d6b50354134aa1d 41,825,853 bytes ``` `scripts/fetch-detector-model.sh` does the fetch, the checksum and an atomic install. The Dockerfile calls it for `/app/models/regions.onnx`; the local recipe in `docs/DEVELOPMENT.md` calls it with no argument. One implementation, because two would have drifted — and the local recipe no longer needs Python at all, only a re-export does. `DOUJIN_MANAGER_DETECTOR_MODEL` already defaulted to that path. It now points at something real. ## Three things only building it could have found 1. **The model was there and unreadable.** `mktemp` creates 0600 and `mv` keeps it. The script runs as root in the image; the server runs as `$APP_UID`. And because the file *exists*, startup registers the detector, tries to load it, and **the container dies on every boot** — a failure that looks like a bad image rather than a bad file mode. `chmod 0644`, pinned by `The_Installed_File_Is_Readable_By_Whoever_Runs_The_Server`. 2. **`.dockerignore` excludes `scripts/`**, so `COPY` failed on a file plainly present in the tree. Un-ignored the one script the build needs, with the exception written next to the exclusion. 3. **Anonymous read had to be settled first.** The build carries no credentials, so a registry that demanded them would have sunk the design rather than inconvenienced it. Verified before a line of the Dockerfile was written: `GET 200, 41825853 bytes`, no auth. ## Verification `scripts/verify.sh` green (`== verify OK (all) ==`) — backend **1,508 passed** / 5 skipped, app 1,530, analyze clean. The part that matters is not the suite, though. The image was built and started: ``` Region detector ready: regions version f3bf7701aebb0185, 3 class(es), input 1024px, 4 thread(s), confidence 0.25 Region detection available: regions version f3bf7701aebb0185 ``` Three classes, and the detector version derived from the model's own hash — which is what ADR 0027's re-sweep gate reads, so an image carrying a new model re-sweeps the library by itself. `FetchDetectorModelScriptTests` covers the script over a `file://` URL: the happy path, the **refusal on a checksum mismatch** with both hashes named and nothing installed, the skip when the destination is already right, the file mode, and one override given without the other. **Nothing in it touches the network or a real model** — 5 tests, 61 ms. ## What did not change **CI still runs without a model.** The weights are for the image, not the test pipeline; `RealModelFactAttribute` keeps the five real-model cases opt-in behind `DOUJIN_MANAGER_DETECTOR_MODEL`, exactly as before. ## Committing the weights, reconsidered It came up and was rejected again, with a number: this repository's entire packed history is **4.95 MiB across 564 commits**; the model is **39.9 MiB** of already-compressed weights. Committing it makes the repo roughly nine times larger, permanently, and `docker-publish.yml` checks out with `fetch-depth: 0` on every push to main. Git LFS is worse here — not installed on the owner's machine, the instance's LFS endpoint answers 404, and a missed install leaves a 130-byte pointer file that the build would copy into the image as "the model". Recorded in ADR 0028 so the next person doesn't re-derive it. ## Also ADR 0027 and ADR 0028 move from Proposed to **Accepted** — 0027 because phase 1 is built end to end (#183, #185, #188, #191, #192), 0028 because this is its implementation. 0028 gains a "what actually landed" section with the package coordinates, the 0644 story, and the note that its promised build-fails-on-a-wrong-checksum test is asserted against the script rather than a full image build per test run. The BACKLOG item open since #185 is closed. ## Timing The image builds on **every push to main**, not just tags — and the package is already uploaded, so this can merge whenever. Nothing has to happen first. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat: ship the detector model inside the image
Some checks failed
CI / build (pull_request) Successful in 32s
Deploy Smoke Test / smoke (pull_request) Failing after 3m0s
CI / test (pull_request) Successful in 2m37s
7cc173adb4
ADR 0028 decided this in September and it has been open in BACKLOG since
#185, because the step it needed was a file at a URL and nobody had put
one there. The export is now published to the Forgejo generic registry
the images already push to, and the build fetches it.

One script does the fetch, the checksum and the atomic install, called
by the Dockerfile for /app/models/regions.onnx and by the local recipe
for a developer's own copy — two implementations of "download and check"
would have drifted.

Three things only building it could have found:

  - mktemp makes 0600 and mv keeps it. The script runs as root and the
    server runs as $APP_UID, so the model was there and unreadable — and
    because the file EXISTS, startup registers the detector, loads it,
    and the container dies every boot. chmod 0644, pinned by a test.
  - .dockerignore excludes scripts/, so COPY failed on a file plainly
    present. Un-ignored the one script the build needs.
  - Anonymous read from the registry had to be verified before any of
    this was written: the build carries no credentials.

Verified by building the image and starting it — "Region detector ready:
regions version f3bf7701aebb0185, 3 class(es), input 1024px".

CI still runs without a model. The weights are for the image, not for
the test pipeline, and RealModelFactAttribute keeps the five real-model
cases opt-in.

ADR 0027 and ADR 0028 move to Accepted; 0028 records what landed.

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

Summary

Summary
Generated on: 09/21/2026 - 06:00:24
Coverage date: 09/21/2026 - 05:58:31 - 09/21/2026 - 06:00:20
Parser: MultiReport (4x Cobertura)
Assemblies: 4
Classes: 522
Files: 215
Line coverage: 96.2% (23488 of 24408)
Covered lines: 23488
Uncovered lines: 920
Coverable lines: 24408
Total lines: 38654
Branch coverage: 80.4% (2545 of 3165)
Covered branches: 2545
Total branches: 3165
Method coverage: Feature is only available for sponsors

Coverage

DoujinManager.ApplicationCore - 96.9%
Name Line Branch
DoujinManager.ApplicationCore 96.9% 95.1%
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.ImageAnalysis 90%
DoujinManager.ApplicationCore.Entities.ImageFile 100%
DoujinManager.ApplicationCore.Entities.ImageRegion 90%
DoujinManager.ApplicationCore.Entities.ImageShards 100% 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 87.5%
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.ImageRegionId 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.DetectedRegion 100%
DoujinManager.ApplicationCore.Ports.ExtractedImage 100%
DoujinManager.ApplicationCore.Ports.ImageInspection 100%
DoujinManager.ApplicationCore.Ports.RegionDetection 100%
DoujinManager.ApplicationCore.Ports.RegionDetectionFailedException 100%
DoujinManager.ApplicationCore.Ports.StoredImage 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% 100%
DoujinManager.ApplicationCore.Services.ITagService 100%
DoujinManager.ApplicationCore.Services.LikePattern 100%
DoujinManager.ApplicationCore.Services.RegionGeometry 100% 100%
DoujinManager.ApplicationCore.Services.RegionPoint 100%
DoujinManager.ApplicationCore.Services.RegionSweepBatch 100%
DoujinManager.ApplicationCore.Services.RegionSweepJournal 100%
DoujinManager.ApplicationCore.Services.RestoreRequest 100%
DoujinManager.ApplicationCore.Services.RestoreTarget 100%
DoujinManager.ApplicationCore.Services.ScheduledBackupJournal 100%
DoujinManager.ApplicationCore.Services.ScheduledBackupOutcome 100% 100%
DoujinManager.ApplicationCore.Services.SearchIndex 100% 100%
DoujinManager.ApplicationCore.Services.SearchKey 100% 100%
DoujinManager.ApplicationCore.Services.ServiceResult 100%
DoujinManager.ApplicationCore.Services.ServiceResult`1 100%
DoujinManager.ApplicationCore.Services.SortKey 100% 90.9%
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.DoujinMergeReport 100%
DoujinManager.ApplicationCore.UseCases.DoujinMergeResult 100%
DoujinManager.ApplicationCore.UseCases.DownloadBackupQuery 100%
DoujinManager.ApplicationCore.UseCases.DuplicateDoujinGroup 100%
DoujinManager.ApplicationCore.UseCases.DuplicateDoujinReference 100%
DoujinManager.ApplicationCore.UseCases.DuplicateImageGroup 100%
DoujinManager.ApplicationCore.UseCases.EntityMergeReport 100%
DoujinManager.ApplicationCore.UseCases.EntityMergeResult`1 100%
DoujinManager.ApplicationCore.UseCases.EntityMergeRoleConflict 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.GetImageRegionsQuery 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.ImageRegions 100%
DoujinManager.ApplicationCore.UseCases.ImageSweepQuery 100%
DoujinManager.ApplicationCore.UseCases.ImageSweepSlice 100%
DoujinManager.ApplicationCore.UseCases.ImportBackupCommand 100%
DoujinManager.ApplicationCore.UseCases.IntegrityCheck 100%
DoujinManager.ApplicationCore.UseCases.IntegrityChecks 93.3% 91.6%
DoujinManager.ApplicationCore.UseCases.IntegrityFinding 100%
DoujinManager.ApplicationCore.UseCases.IntegrityRepairResult 100%
DoujinManager.ApplicationCore.UseCases.IntegrityRepairs 96.9% 95.8%
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 100%
DoujinManager.ApplicationCore.UseCases.ListTagsQuery 100%
DoujinManager.ApplicationCore.UseCases.ListVariantsQuery 100%
DoujinManager.ApplicationCore.UseCases.MergeCharactersCommand 100%
DoujinManager.ApplicationCore.UseCases.MergeCirclesCommand 100%
DoujinManager.ApplicationCore.UseCases.MergedEntity 100%
DoujinManager.ApplicationCore.UseCases.MergeDoujinsCommand 100%
DoujinManager.ApplicationCore.UseCases.MergePeopleCommand 100%
DoujinManager.ApplicationCore.UseCases.MergeRoleConflict 100%
DoujinManager.ApplicationCore.UseCases.MergeSeriesCommand 100%
DoujinManager.ApplicationCore.UseCases.ReadinessReport 100%
DoujinManager.ApplicationCore.UseCases.RegionDetectionStatus 100%
DoujinManager.ApplicationCore.UseCases.RegionSweepResult 100%
DoujinManager.ApplicationCore.UseCases.RemoveCharacterAliasCommand 100%
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 100%
DoujinManager.ApplicationCore.UseCases.RemoveSeriesCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveTagAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveTagCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveTitleCommand 100%
DoujinManager.ApplicationCore.UseCases.ReorderChaptersCommand 100%
DoujinManager.ApplicationCore.UseCases.ReorderPagesCommand 100%
DoujinManager.ApplicationCore.UseCases.RepairIntegrityFindingCommand 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.StoredRegion 100%
DoujinManager.ApplicationCore.UseCases.SweepRegionsCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateChapterCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateCharacterCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateCircleCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateDoujinCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdatePageChapterCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdatePersonCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateSeriesCommand 100%
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.ApplicationCore.UseCases.VerifyBackupCommand 100%
DoujinManager.Infrastructure - 96.8%
Name Line Branch
DoujinManager.Infrastructure 96.8% 86.6%
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.ImageAnalysisConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.ImageFileConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.ImageRegionConfiguration 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.AddEntitySearchIndex 96.5%
DoujinManager.Infrastructure.Data.Migrations.AddEntitySearchKey 96.9%
DoujinManager.Infrastructure.Data.Migrations.AddImageFileSha256Index 99.4%
DoujinManager.Infrastructure.Data.Migrations.AddImageRegionDetection 99.4%
DoujinManager.Infrastructure.Data.Migrations.AddScaleIndexes 98.2%
DoujinManager.Infrastructure.Data.Migrations.AddTagAliases 99.3%
DoujinManager.Infrastructure.Data.Migrations.AddTitleSearchKey 99.1%
DoujinManager.Infrastructure.Data.Migrations.AddTitleSortKey 98.6%
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.StartupMigration 100% 100%
DoujinManager.Infrastructure.Data.StronglyTypedIdConverterFactory 100%
DoujinManager.Infrastructure.Data.WalCheckpointResult 100%
DoujinManager.Infrastructure.Detection.DetectionOptions 100%
DoujinManager.Infrastructure.Detection.LetterboxTransform 100%
DoujinManager.Infrastructure.Detection.MaskContour 100% 97.1%
DoujinManager.Infrastructure.Detection.OnnxRegionDetector 20.1% 20.8%
DoujinManager.Infrastructure.Detection.PolygonSimplifier 100% 92.3%
DoujinManager.Infrastructure.Detection.RegionClassMap 100% 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.BackupInspection 100%
DoujinManager.Infrastructure.Services.BackupReceipts 90% 87.5%
DoujinManager.Infrastructure.Services.BackupRestoreCoordinator 71.6% 69.2%
DoujinManager.Infrastructure.Services.BackupRestoreService 93.6% 88%
DoujinManager.Infrastructure.Services.BackupService 86.7% 85.4%
DoujinManager.Infrastructure.Services.BackupStagingArea 100% 100%
DoujinManager.Infrastructure.Services.BackupVerifier 92.8% 89.1%
DoujinManager.Infrastructure.Services.ChapterService 98.1% 81.2%
DoujinManager.Infrastructure.Services.CharacterService 100% 77.7%
DoujinManager.Infrastructure.Services.CircleService 100% 77.7%
DoujinManager.Infrastructure.Services.DoujinOrdering 100% 100%
DoujinManager.Infrastructure.Services.DoujinService 100% 77.5%
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% 83.3%
DoujinManager.Infrastructure.Services.RestoreMarker 81.8% 83.3%
DoujinManager.Infrastructure.Services.RestoreOutcome 100%
DoujinManager.Infrastructure.Services.SearchService 100% 99%
DoujinManager.Infrastructure.Services.SeriesService 100% 77.7%
DoujinManager.Infrastructure.Services.TagService 100% 88.8%
DoujinManager.Infrastructure.Services.VariantService 96% 78.9%
DoujinManager.Infrastructure.Storage.DirectorySize 100% 100%
DoujinManager.Infrastructure.Storage.FilesystemImageStorage 100% 100%
DoujinManager.Infrastructure.Storage.FilesystemThumbnailStorage 97.8% 91.6%
DoujinManager.Infrastructure.UseCases 97.6% 90.3%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__CoverImageHelper
100% 100%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__PrimaryTitleHelper
100% 87.5%
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% 100%
DoujinManager.Infrastructure.UseCases.CreateCircleUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateDoujinUseCase 94.4% 94.1%
DoujinManager.Infrastructure.UseCases.CreatePersonUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateSeriesUseCase 100% 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.EntityDoujinLink 100%
DoujinManager.Infrastructure.UseCases.EntityMerge 95.4% 93.2%
DoujinManager.Infrastructure.UseCases.FindImageBySha256UseCase 97.3% 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.GetImageRegionsUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetImageUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetIntegrityReportUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetPersonUseCase 100%
DoujinManager.Infrastructure.UseCases.GetRegionDetectionStatusUseCase 100% 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.IntegrityQueries 83.3% 50%
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 100%
DoujinManager.Infrastructure.UseCases.ListTagsUseCase 100%
DoujinManager.Infrastructure.UseCases.ListVariantsUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.MergeCharactersUseCase 100%
DoujinManager.Infrastructure.UseCases.MergeCirclesUseCase 100%
DoujinManager.Infrastructure.UseCases.MergeDoujinsUseCase 100% 98.4%
DoujinManager.Infrastructure.UseCases.MergePeopleUseCase 100%
DoujinManager.Infrastructure.UseCases.MergeSeriesUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveCharacterAliasUseCase 100% 50%
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 100% 50%
DoujinManager.Infrastructure.UseCases.RemoveSeriesUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveTagAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemoveTagUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveTitleUseCase 100%
DoujinManager.Infrastructure.UseCases.ReorderChaptersUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ReorderPagesUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.RepairIntegrityFindingUseCase 96.1% 80.7%
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.SweepImageTreeUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.SweepRegionsUseCase 96.5% 100%
DoujinManager.Infrastructure.UseCases.UpdateChapterUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.UpdateCharacterUseCase 100% 83.3%
DoujinManager.Infrastructure.UseCases.UpdateCircleUseCase 100% 83.3%
DoujinManager.Infrastructure.UseCases.UpdateDoujinUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdatePageChapterUseCase 93.7% 87.5%
DoujinManager.Infrastructure.UseCases.UpdatePersonUseCase 100% 83.3%
DoujinManager.Infrastructure.UseCases.UpdateSeriesUseCase 100% 83.3%
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.Infrastructure.UseCases.VerifyBackupUseCase 100%
System.Text.RegularExpressions.Generated 85% 67.3%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FC6ABDEC12267CC0
9CDB080D11D98B3E0D3ADB24949B4D7D90DD87AD1CEB5845F__Entry_0
88% 71.4%
DoujinManager.RestAdapter - 98.9%
Name Line Branch
DoujinManager.RestAdapter 98.9% 81.9%
DoujinManager.RestAdapter.Auth.StaticBearerTokenAuthMiddleware 100% 91.6%
DoujinManager.RestAdapter.Configuration.ImageOptions 100%
DoujinManager.RestAdapter.Configuration.IntegrityOptions 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 100% 100%
DoujinManager.RestAdapter.Dtos.ChapterDto 100%
DoujinManager.RestAdapter.Dtos.CharacterDto 100%
DoujinManager.RestAdapter.Dtos.CharacterMergeResultDto 100%
DoujinManager.RestAdapter.Dtos.CircleDto 100%
DoujinManager.RestAdapter.Dtos.CircleMergeResultDto 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.DetectionStatusDto 100% 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.EntityMergeReportDto 100%
DoujinManager.RestAdapter.Dtos.EntityMergeRoleConflictDto 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.ImageRegionDto 100% 100%
DoujinManager.RestAdapter.Dtos.ImageRegionsDto 100% 100%
DoujinManager.RestAdapter.Dtos.ImageSweepSliceDto 100%
DoujinManager.RestAdapter.Dtos.IntegrityCheckDto 100%
DoujinManager.RestAdapter.Dtos.IntegrityFindingDto 100%
DoujinManager.RestAdapter.Dtos.IntegrityRepairResultDto 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.MergedEntityDto 100%
DoujinManager.RestAdapter.Dtos.MergeDoujinsDto 100%
DoujinManager.RestAdapter.Dtos.MergeEntitiesDto 100%
DoujinManager.RestAdapter.Dtos.MergeReportDto 100%
DoujinManager.RestAdapter.Dtos.MergeResultDto 100%
DoujinManager.RestAdapter.Dtos.MergeRoleConflictDto 100%
DoujinManager.RestAdapter.Dtos.PageDetailDto 100%
DoujinManager.RestAdapter.Dtos.PersonDto 100%
DoujinManager.RestAdapter.Dtos.PersonMergeResultDto 100%
DoujinManager.RestAdapter.Dtos.RegionSweepBatchDto 16.6% 50%
DoujinManager.RestAdapter.Dtos.ReorderChaptersDto 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.SeriesMergeResultDto 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 100%
DoujinManager.RestAdapter.Dtos.UpdateCircleDto 100%
DoujinManager.RestAdapter.Dtos.UpdateDoujinDto 100%
DoujinManager.RestAdapter.Dtos.UpdatePageChapterDto 100%
DoujinManager.RestAdapter.Dtos.UpdatePersonDto 100%
DoujinManager.RestAdapter.Dtos.UpdateSeriesDto 100%
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.2%
DoujinManager.RestAdapter.Endpoints.DetectionEndpoints 100%
DoujinManager.RestAdapter.Endpoints.DoujinEndpoints 98.1% 55.5%
DoujinManager.RestAdapter.Endpoints.HealthEndpoints 100%
DoujinManager.RestAdapter.Endpoints.ImageEndpoints 97.6% 75%
DoujinManager.RestAdapter.Endpoints.IntegrityEndpoints 100%
DoujinManager.RestAdapter.Endpoints.MetadataEndpoints 99.3% 50%
DoujinManager.RestAdapter.Endpoints.PageMapper 100% 58.3%
DoujinManager.RestAdapter.Endpoints.PaginationParams 100%
DoujinManager.RestAdapter.Endpoints.SearchEndpoints 100% 50%
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% 94.1%
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 99.8% 94.4%
Microsoft.Extensions.Validation.Generated.<ValidatableInfoResolver_g>FB9B0C
E5CB12CEAC5BBBEA65844E1C05F9ADA4DA38B848075C41304FC6A1ABF77__ValidationAttr
ibuteCache
100% 62.5%
System.Runtime.CompilerServices 0%
DoujinManager.Server - 67.9%
Name Line Branch
DoujinManager.Server 67.9% 36.5%
DoujinManager.Server.BackupRegistration 100% 100%
DoujinManager.Server.BackupScheduleOptions 100%
DoujinManager.Server.BearerSecuritySchemeTransformer 100% 78.5%
DoujinManager.Server.DatabaseRegistration 89.6%
DoujinManager.Server.DetectionConfiguration 100% 100%
DoujinManager.Server.DetectionRegistration 68.7% 56.2%
DoujinManager.Server.DetectionSettings 100%
DoujinManager.Server.ImageInfrastructureRegistration 100%
DoujinManager.Server.ProxyAwareServerTransformer 100% 75%
DoujinManager.Server.ReadinessRegistration 100%
DoujinManager.Server.RegionDetectionService 78.4% 80%
DoujinManager.Server.ScalarUi 100%
DoujinManager.Server.ScheduledBackupConfiguration 100% 91.1%
DoujinManager.Server.ScheduledBackupService 80.2% 75%
DoujinManager.Server.SqliteRuntimeConfiguration 96.5% 95%
DoujinManager.Server.UseCaseRegistrationHelper 100%
Microsoft.AspNetCore.OpenApi.Generated 1.3% 0%
Program 85.4% 30%
System.Runtime.CompilerServices 0%
<!-- coverage-comment --> # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 09/21/2026 - 06:00:24 | | Coverage date: | 09/21/2026 - 05:58:31 - 09/21/2026 - 06:00:20 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 4 | | Classes: | 522 | | Files: | 215 | | **Line coverage:** | 96.2% (23488 of 24408) | | Covered lines: | 23488 | | Uncovered lines: | 920 | | Coverable lines: | 24408 | | Total lines: | 38654 | | **Branch coverage:** | 80.4% (2545 of 3165) | | Covered branches: | 2545 | | Total branches: | 3165 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>DoujinManager.ApplicationCore - 96.9%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.ApplicationCore**|**96.9%**|**95.1%**| |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.ImageAnalysis|90%|| |DoujinManager.ApplicationCore.Entities.ImageFile|100%|| |DoujinManager.ApplicationCore.Entities.ImageRegion|90%|| |DoujinManager.ApplicationCore.Entities.ImageShards|100%|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|87.5%|| |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.ImageRegionId|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.DetectedRegion|100%|| |DoujinManager.ApplicationCore.Ports.ExtractedImage|100%|| |DoujinManager.ApplicationCore.Ports.ImageInspection|100%|| |DoujinManager.ApplicationCore.Ports.RegionDetection|100%|| |DoujinManager.ApplicationCore.Ports.RegionDetectionFailedException|100%|| |DoujinManager.ApplicationCore.Ports.StoredImage|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%|100%| |DoujinManager.ApplicationCore.Services.ITagService|100%|| |DoujinManager.ApplicationCore.Services.LikePattern|100%|| |DoujinManager.ApplicationCore.Services.RegionGeometry|100%|100%| |DoujinManager.ApplicationCore.Services.RegionPoint|100%|| |DoujinManager.ApplicationCore.Services.RegionSweepBatch|100%|| |DoujinManager.ApplicationCore.Services.RegionSweepJournal|100%|| |DoujinManager.ApplicationCore.Services.RestoreRequest|100%|| |DoujinManager.ApplicationCore.Services.RestoreTarget|100%|| |DoujinManager.ApplicationCore.Services.ScheduledBackupJournal|100%|| |DoujinManager.ApplicationCore.Services.ScheduledBackupOutcome|100%|100%| |DoujinManager.ApplicationCore.Services.SearchIndex|100%|100%| |DoujinManager.ApplicationCore.Services.SearchKey|100%|100%| |DoujinManager.ApplicationCore.Services.ServiceResult|100%|| |DoujinManager.ApplicationCore.Services.ServiceResult`1|100%|| |DoujinManager.ApplicationCore.Services.SortKey|100%|90.9%| |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.DoujinMergeReport|100%|| |DoujinManager.ApplicationCore.UseCases.DoujinMergeResult|100%|| |DoujinManager.ApplicationCore.UseCases.DownloadBackupQuery|100%|| |DoujinManager.ApplicationCore.UseCases.DuplicateDoujinGroup|100%|| |DoujinManager.ApplicationCore.UseCases.DuplicateDoujinReference|100%|| |DoujinManager.ApplicationCore.UseCases.DuplicateImageGroup|100%|| |DoujinManager.ApplicationCore.UseCases.EntityMergeReport|100%|| |DoujinManager.ApplicationCore.UseCases.EntityMergeResult`1|100%|| |DoujinManager.ApplicationCore.UseCases.EntityMergeRoleConflict|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.GetImageRegionsQuery|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.ImageRegions|100%|| |DoujinManager.ApplicationCore.UseCases.ImageSweepQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ImageSweepSlice|100%|| |DoujinManager.ApplicationCore.UseCases.ImportBackupCommand|100%|| |DoujinManager.ApplicationCore.UseCases.IntegrityCheck|100%|| |DoujinManager.ApplicationCore.UseCases.IntegrityChecks|93.3%|91.6%| |DoujinManager.ApplicationCore.UseCases.IntegrityFinding|100%|| |DoujinManager.ApplicationCore.UseCases.IntegrityRepairResult|100%|| |DoujinManager.ApplicationCore.UseCases.IntegrityRepairs|96.9%|95.8%| |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|100%|| |DoujinManager.ApplicationCore.UseCases.ListTagsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListVariantsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.MergeCharactersCommand|100%|| |DoujinManager.ApplicationCore.UseCases.MergeCirclesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.MergedEntity|100%|| |DoujinManager.ApplicationCore.UseCases.MergeDoujinsCommand|100%|| |DoujinManager.ApplicationCore.UseCases.MergePeopleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.MergeRoleConflict|100%|| |DoujinManager.ApplicationCore.UseCases.MergeSeriesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.ReadinessReport|100%|| |DoujinManager.ApplicationCore.UseCases.RegionDetectionStatus|100%|| |DoujinManager.ApplicationCore.UseCases.RegionSweepResult|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCharacterAliasCommand|100%|| |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|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveSeriesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveTagAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveTitleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.ReorderChaptersCommand|100%|| |DoujinManager.ApplicationCore.UseCases.ReorderPagesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RepairIntegrityFindingCommand|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.StoredRegion|100%|| |DoujinManager.ApplicationCore.UseCases.SweepRegionsCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateChapterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateCharacterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateCircleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateDoujinCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdatePageChapterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdatePersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateSeriesCommand|100%|| |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.ApplicationCore.UseCases.VerifyBackupCommand|100%|| </details> <details><summary>DoujinManager.Infrastructure - 96.8%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.Infrastructure**|**96.8%**|**86.6%**| |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.ImageAnalysisConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.ImageFileConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.ImageRegionConfiguration|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.AddEntitySearchIndex|96.5%|| |DoujinManager.Infrastructure.Data.Migrations.AddEntitySearchKey|96.9%|| |DoujinManager.Infrastructure.Data.Migrations.AddImageFileSha256Index|99.4%|| |DoujinManager.Infrastructure.Data.Migrations.AddImageRegionDetection|99.4%|| |DoujinManager.Infrastructure.Data.Migrations.AddScaleIndexes|98.2%|| |DoujinManager.Infrastructure.Data.Migrations.AddTagAliases|99.3%|| |DoujinManager.Infrastructure.Data.Migrations.AddTitleSearchKey|99.1%|| |DoujinManager.Infrastructure.Data.Migrations.AddTitleSortKey|98.6%|| |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.StartupMigration|100%|100%| |DoujinManager.Infrastructure.Data.StronglyTypedIdConverterFactory|100%|| |DoujinManager.Infrastructure.Data.WalCheckpointResult|100%|| |DoujinManager.Infrastructure.Detection.DetectionOptions|100%|| |DoujinManager.Infrastructure.Detection.LetterboxTransform|100%|| |DoujinManager.Infrastructure.Detection.MaskContour|100%|97.1%| |DoujinManager.Infrastructure.Detection.OnnxRegionDetector|20.1%|20.8%| |DoujinManager.Infrastructure.Detection.PolygonSimplifier|100%|92.3%| |DoujinManager.Infrastructure.Detection.RegionClassMap|100%|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.BackupInspection|100%|| |DoujinManager.Infrastructure.Services.BackupReceipts|90%|87.5%| |DoujinManager.Infrastructure.Services.BackupRestoreCoordinator|71.6%|69.2%| |DoujinManager.Infrastructure.Services.BackupRestoreService|93.6%|88%| |DoujinManager.Infrastructure.Services.BackupService|86.7%|85.4%| |DoujinManager.Infrastructure.Services.BackupStagingArea|100%|100%| |DoujinManager.Infrastructure.Services.BackupVerifier|92.8%|89.1%| |DoujinManager.Infrastructure.Services.ChapterService|98.1%|81.2%| |DoujinManager.Infrastructure.Services.CharacterService|100%|77.7%| |DoujinManager.Infrastructure.Services.CircleService|100%|77.7%| |DoujinManager.Infrastructure.Services.DoujinOrdering|100%|100%| |DoujinManager.Infrastructure.Services.DoujinService|100%|77.5%| |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%|83.3%| |DoujinManager.Infrastructure.Services.RestoreMarker|81.8%|83.3%| |DoujinManager.Infrastructure.Services.RestoreOutcome|100%|| |DoujinManager.Infrastructure.Services.SearchService|100%|99%| |DoujinManager.Infrastructure.Services.SeriesService|100%|77.7%| |DoujinManager.Infrastructure.Services.TagService|100%|88.8%| |DoujinManager.Infrastructure.Services.VariantService|96%|78.9%| |DoujinManager.Infrastructure.Storage.DirectorySize|100%|100%| |DoujinManager.Infrastructure.Storage.FilesystemImageStorage|100%|100%| |DoujinManager.Infrastructure.Storage.FilesystemThumbnailStorage|97.8%|91.6%| |DoujinManager.Infrastructure.UseCases|97.6%|90.3%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__CoverImageHelper|100%|100%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__PrimaryTitleHelper|100%|87.5%| |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%|100%| |DoujinManager.Infrastructure.UseCases.CreateCircleUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateDoujinUseCase|94.4%|94.1%| |DoujinManager.Infrastructure.UseCases.CreatePersonUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateSeriesUseCase|100%|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.EntityDoujinLink|100%|| |DoujinManager.Infrastructure.UseCases.EntityMerge|95.4%|93.2%| |DoujinManager.Infrastructure.UseCases.FindImageBySha256UseCase|97.3%|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.GetImageRegionsUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetImageUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetIntegrityReportUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetPersonUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetRegionDetectionStatusUseCase|100%|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.IntegrityQueries|83.3%|50%| |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|100%|| |DoujinManager.Infrastructure.UseCases.ListTagsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListVariantsUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.MergeCharactersUseCase|100%|| |DoujinManager.Infrastructure.UseCases.MergeCirclesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.MergeDoujinsUseCase|100%|98.4%| |DoujinManager.Infrastructure.UseCases.MergePeopleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.MergeSeriesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveCharacterAliasUseCase|100%|50%| |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|100%|50%| |DoujinManager.Infrastructure.UseCases.RemoveSeriesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveTagAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemoveTagUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveTitleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ReorderChaptersUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ReorderPagesUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.RepairIntegrityFindingUseCase|96.1%|80.7%| |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.SweepImageTreeUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.SweepRegionsUseCase|96.5%|100%| |DoujinManager.Infrastructure.UseCases.UpdateChapterUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.UpdateCharacterUseCase|100%|83.3%| |DoujinManager.Infrastructure.UseCases.UpdateCircleUseCase|100%|83.3%| |DoujinManager.Infrastructure.UseCases.UpdateDoujinUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdatePageChapterUseCase|93.7%|87.5%| |DoujinManager.Infrastructure.UseCases.UpdatePersonUseCase|100%|83.3%| |DoujinManager.Infrastructure.UseCases.UpdateSeriesUseCase|100%|83.3%| |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.Infrastructure.UseCases.VerifyBackupUseCase|100%|| |System.Text.RegularExpressions.Generated|85%|67.3%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FC6ABDEC12267CC0<br/>9CDB080D11D98B3E0D3ADB24949B4D7D90DD87AD1CEB5845F__Entry_0|88%|71.4%| </details> <details><summary>DoujinManager.RestAdapter - 98.9%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.RestAdapter**|**98.9%**|**81.9%**| |DoujinManager.RestAdapter.Auth.StaticBearerTokenAuthMiddleware|100%|91.6%| |DoujinManager.RestAdapter.Configuration.ImageOptions|100%|| |DoujinManager.RestAdapter.Configuration.IntegrityOptions|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|100%|100%| |DoujinManager.RestAdapter.Dtos.ChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.CharacterDto|100%|| |DoujinManager.RestAdapter.Dtos.CharacterMergeResultDto|100%|| |DoujinManager.RestAdapter.Dtos.CircleDto|100%|| |DoujinManager.RestAdapter.Dtos.CircleMergeResultDto|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.DetectionStatusDto|100%|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.EntityMergeReportDto|100%|| |DoujinManager.RestAdapter.Dtos.EntityMergeRoleConflictDto|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.ImageRegionDto|100%|100%| |DoujinManager.RestAdapter.Dtos.ImageRegionsDto|100%|100%| |DoujinManager.RestAdapter.Dtos.ImageSweepSliceDto|100%|| |DoujinManager.RestAdapter.Dtos.IntegrityCheckDto|100%|| |DoujinManager.RestAdapter.Dtos.IntegrityFindingDto|100%|| |DoujinManager.RestAdapter.Dtos.IntegrityRepairResultDto|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.MergedEntityDto|100%|| |DoujinManager.RestAdapter.Dtos.MergeDoujinsDto|100%|| |DoujinManager.RestAdapter.Dtos.MergeEntitiesDto|100%|| |DoujinManager.RestAdapter.Dtos.MergeReportDto|100%|| |DoujinManager.RestAdapter.Dtos.MergeResultDto|100%|| |DoujinManager.RestAdapter.Dtos.MergeRoleConflictDto|100%|| |DoujinManager.RestAdapter.Dtos.PageDetailDto|100%|| |DoujinManager.RestAdapter.Dtos.PersonDto|100%|| |DoujinManager.RestAdapter.Dtos.PersonMergeResultDto|100%|| |DoujinManager.RestAdapter.Dtos.RegionSweepBatchDto|16.6%|50%| |DoujinManager.RestAdapter.Dtos.ReorderChaptersDto|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.SeriesMergeResultDto|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|100%|| |DoujinManager.RestAdapter.Dtos.UpdateCircleDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateDoujinDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdatePageChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdatePersonDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateSeriesDto|100%|| |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.2%|| |DoujinManager.RestAdapter.Endpoints.DetectionEndpoints|100%|| |DoujinManager.RestAdapter.Endpoints.DoujinEndpoints|98.1%|55.5%| |DoujinManager.RestAdapter.Endpoints.HealthEndpoints|100%|| |DoujinManager.RestAdapter.Endpoints.ImageEndpoints|97.6%|75%| |DoujinManager.RestAdapter.Endpoints.IntegrityEndpoints|100%|| |DoujinManager.RestAdapter.Endpoints.MetadataEndpoints|99.3%|50%| |DoujinManager.RestAdapter.Endpoints.PageMapper|100%|58.3%| |DoujinManager.RestAdapter.Endpoints.PaginationParams|100%|| |DoujinManager.RestAdapter.Endpoints.SearchEndpoints|100%|50%| |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%|94.1%| |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|99.8%|94.4%| |Microsoft.Extensions.Validation.Generated.<ValidatableInfoResolver_g>FB9B0C<br/>E5CB12CEAC5BBBEA65844E1C05F9ADA4DA38B848075C41304FC6A1ABF77__ValidationAttr<br/>ibuteCache|100%|62.5%| |System.Runtime.CompilerServices|0%|| </details> <details><summary>DoujinManager.Server - 67.9%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.Server**|**67.9%**|**36.5%**| |DoujinManager.Server.BackupRegistration|100%|100%| |DoujinManager.Server.BackupScheduleOptions|100%|| |DoujinManager.Server.BearerSecuritySchemeTransformer|100%|78.5%| |DoujinManager.Server.DatabaseRegistration|89.6%|| |DoujinManager.Server.DetectionConfiguration|100%|100%| |DoujinManager.Server.DetectionRegistration|68.7%|56.2%| |DoujinManager.Server.DetectionSettings|100%|| |DoujinManager.Server.ImageInfrastructureRegistration|100%|| |DoujinManager.Server.ProxyAwareServerTransformer|100%|75%| |DoujinManager.Server.ReadinessRegistration|100%|| |DoujinManager.Server.RegionDetectionService|78.4%|80%| |DoujinManager.Server.ScalarUi|100%|| |DoujinManager.Server.ScheduledBackupConfiguration|100%|91.1%| |DoujinManager.Server.ScheduledBackupService|80.2%|75%| |DoujinManager.Server.SqliteRuntimeConfiguration|96.5%|95%| |DoujinManager.Server.UseCaseRegistrationHelper|100%|| |Microsoft.AspNetCore.OpenApi.Generated|1.3%|0%| |Program|85.4%|30%| |System.Runtime.CompilerServices|0%|| </details>
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! A PR whose entire reason to exist is that a checksum refuses to lie~ ♡ An atomic mktemp→verify→mv install, a chmod 0644 somebody actually traced through uid 1654, one script with two callers so the implementations cannot drift, and tests that poke a real refusal instead of a green smoke puff. I pulled your 41,825,853 bytes down myself and hashed them: f3bf7701…aa1d, anonymous 200, checksum OK — every number in the PR body is a true number. This is the good stuff. But you knew I'd find the one seam~ ♪

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

⛔ These need fixing before I'm satisfied~

  1. scripts/fetch-detector-model.sh:36–39 — the together-guard's second arm is unpinned, and I can prove it. The guard exists to name two mistakes — URL without SHA256, and SHA256 without URL — but FetchDetectorModelScriptTests only ever exercises the first. I mutated the script in a review clone (deleted the || [[ -z "${DETECTOR_MODEL_URL:-}" && -n "${DETECTOR_MODEL_SHA256:-}" ]] disjunct, leaving everything else intact) and ran the suite: 5/5 green. The tests cannot detect the loss of half the guard. Your own house rule — written into BashFactAttribute's doc comment and LinuxFactAttribute before it — is that a branch without a pin isn't covered; ADR 0028's verification section says a check that has never been seen to fail might be spelled wrong. This half has literally never been seen to fail~ fufu~ you wouldn't leave an unpinned branch in production, would you? ♡
    Fix: mirror the existing case — A_Checksum_Without_A_Url_Is_Refused_Before_Anything_Is_Downloaded: run the script with DETECTOR_MODEL_URL empty and DETECTOR_MODEL_SHA256 set to the source's real hash, assert exit 2, nothing installed, and "together" in stderr. ~15 lines, and the probe that just went silently green goes red.

💡 Little ideas (non-blocking)~

  1. scripts/fetch-detector-model.sh:66 — [[ "$actual" != "$SHA256" ]] is case-sensitive, so a custom pin written in uppercase hex (DETECTOR_MODEL_SHA256=AB…) is refused as a mismatch even when the bytes are right. It fails safe and loud — both hashes are printed side by side — so I'm only pouting, not blocking~ A tr 'A-F' 'a-f' on the override would make it forgiving.

✅ What I liked~

  • The atomic install discipline. Download to a mktemp beside the destination, verify, mv, chmod, clear the trap — an interrupted or corrupt download can never leave a corpse at the path the server loads from. ♪
  • The 0644 story. You didn't patch a mystery; you ran it in a real aspnet:10.0 container, watched the container die on every boot, and pinned the mode with a test asserting readable-by-all, writable-by-none. The_Installed_File_Is_Readable_By_Whoever_Runs_The_Server is load-bearing and now it cannot regress.
  • One implementation, two callers — the Dockerfile and the docs/DEVELOPMENT.md recipe call the same script, and the local recipe needs no Python at all anymore. DRY exactly where drift would have bitten.
  • Tests over file:// — nothing touches the network or a real model, the refusal case asserts both hashes named and nothing installed, the skip case proves non-fetching by pointing the URL at nothing, and the empty-ARG-as-unset semantics the container build produces are documented in the test itself. 5/5 green here in 110 ms, and my probes agree they're directional: chmod deleted → exactly the mode test red; checksum comparison if false → exactly the refusal test red; skip block deleted → exactly the skip test red; whole guard deleted → exactly the guard test red.
  • The paper trail. ADR 0028's "what actually landed" records the package coordinates, the 0644 line, and the LFS rejection with numbers — 4.95 MiB of history vs 39.9 MiB of weights, and the arithmetic checks out. BACKLOG swept clean; DEPLOYMENT.md and the compose comments agree with the code; the ADR status transitions cite the PRs that earned them.

Merge the mirrored test in and this is an enthusiastic approve — the design itself I have zero quarrel with~ ♡


Automated review by Jibril · 2026-09-21
CI/CD: ci.yml build+test green for 7cc173a (run 880); deploy-smoke red on head — but chronically red on main and every prior PR at the same ~180 s wall long before this PR, pre-existing, not a regression here · Local checks: 5/5 FetchDetectorModelScriptTests green (110 ms); full RestAdapter suite 1046 passed / 5 skipped (pre-existing real-model skips); model downloaded and sha256-verified against the pin; 5 mutation probes (4 red-on-exactly-target, 1 proving the blocker above); review clone restored pristine at 7cc173a

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! A PR whose entire reason to exist is that a checksum refuses to lie~ ♡ An atomic `mktemp`→verify→`mv` install, a `chmod 0644` somebody actually traced through uid 1654, one script with two callers so the implementations cannot drift, and tests that poke a real refusal instead of a green smoke puff. I pulled your 41,825,853 bytes down myself and hashed them: `f3bf7701…aa1d`, anonymous 200, checksum OK — every number in the PR body is a true number. This is the good stuff. But you knew I'd find the one seam~ ♪ ### Verdict: ⛔ I can't let this pass~ ♡ #### ⛔ These need fixing before I'm satisfied~ 1. **scripts/fetch-detector-model.sh:36–39 — the together-guard's second arm is unpinned, and I can prove it.** The guard exists to name two mistakes — URL without SHA256, and SHA256 without URL — but `FetchDetectorModelScriptTests` only ever exercises the first. I mutated the script in a review clone (deleted the `|| [[ -z "${DETECTOR_MODEL_URL:-}" && -n "${DETECTOR_MODEL_SHA256:-}" ]]` disjunct, leaving everything else intact) and ran the suite: **5/5 green**. The tests cannot detect the loss of half the guard. Your own house rule — written into `BashFactAttribute`'s doc comment and `LinuxFactAttribute` before it — is that a branch without a pin isn't covered; ADR 0028's verification section says a check that has never been seen to fail might be spelled wrong. This half has literally never been seen to fail~ fufu~ you wouldn't leave an unpinned branch in production, would you? ♡ Fix: mirror the existing case — `A_Checksum_Without_A_Url_Is_Refused_Before_Anything_Is_Downloaded`: run the script with `DETECTOR_MODEL_URL` empty and `DETECTOR_MODEL_SHA256` set to the source's real hash, assert exit 2, nothing installed, and `"together"` in stderr. ~15 lines, and the probe that just went silently green goes red. #### 💡 Little ideas (non-blocking)~ 1. **scripts/fetch-detector-model.sh:66** — `[[ "$actual" != "$SHA256" ]]` is case-sensitive, so a custom pin written in uppercase hex (`DETECTOR_MODEL_SHA256=AB…`) is refused as a mismatch even when the bytes are right. It fails safe and loud — both hashes are printed side by side — so I'm only pouting, not blocking~ A `tr 'A-F' 'a-f'` on the override would make it forgiving. #### ✅ What I liked~ - **The atomic install discipline.** Download to a `mktemp` beside the destination, verify, `mv`, `chmod`, clear the trap — an interrupted or corrupt download can never leave a corpse at the path the server loads from. ♪ - **The 0644 story.** You didn't patch a mystery; you ran it in a real `aspnet:10.0` container, watched the container die on every boot, and pinned the mode with a test asserting readable-by-all, writable-by-none. `The_Installed_File_Is_Readable_By_Whoever_Runs_The_Server` is load-bearing and now it cannot regress. - **One implementation, two callers** — the Dockerfile and the `docs/DEVELOPMENT.md` recipe call the same script, and the local recipe needs no Python at all anymore. DRY exactly where drift would have bitten. - **Tests over `file://`** — nothing touches the network or a real model, the refusal case asserts both hashes named and nothing installed, the skip case proves non-fetching by pointing the URL at nothing, and the empty-ARG-as-unset semantics the container build produces are documented in the test itself. 5/5 green here in 110 ms, and my probes agree they're directional: chmod deleted → exactly the mode test red; checksum comparison `if false` → exactly the refusal test red; skip block deleted → exactly the skip test red; whole guard deleted → exactly the guard test red. - **The paper trail.** ADR 0028's "what actually landed" records the package coordinates, the 0644 line, and the LFS rejection with numbers — 4.95 MiB of history vs 39.9 MiB of weights, and the arithmetic checks out. BACKLOG swept clean; `DEPLOYMENT.md` and the compose comments agree with the code; the ADR status transitions cite the PRs that earned them. Merge the mirrored test in and this is an enthusiastic approve — the design itself I have zero quarrel with~ ♡ --- *Automated review by Jibril · 2026-09-21* *CI/CD: ci.yml build+test green for 7cc173a (run 880); deploy-smoke red on head — but chronically red on main and every prior PR at the same ~180 s wall long before this PR, pre-existing, not a regression here · Local checks: 5/5 FetchDetectorModelScriptTests green (110 ms); full RestAdapter suite 1046 passed / 5 skipped (pre-existing real-model skips); model downloaded and sha256-verified against the pin; 5 mutation probes (4 red-on-exactly-target, 1 proving the blocker above); review clone restored pristine at 7cc173a*
fix: pin the other half of the together-guard
Some checks failed
CI / build (pull_request) Successful in 32s
Deploy Smoke Test / smoke (pull_request) Failing after 3m1s
CI / test (pull_request) Successful in 2m27s
a37ebaba51
The guard names two mistakes — a URL without a checksum and a checksum
without a URL — and only the first was tested. A review probe deleted
the second disjunct and all five tests stayed green, which is the
definition of an unpinned branch. Mirrored the existing case; the same
probe now goes red.

Half a guard is the half nobody notices going missing: a checksum pinned
against the built-in URL would fetch the built-in model and verify it
against somebody else's hash.

Also, from the same review: the pin is folded to lower case before
comparison. sha256sum prints lower case, so an override written in
upper-case hex was refused as a mismatch — with both hashes printed side
by side spelling the same value twice, which is a maddening thing to
read at the bottom of a build log.

Five probes, all red on exactly their own test, including the one the
review used.

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

Fixed at a37ebab.

The blocker: half a guard

You're right, and the way you proved it is the part worth keeping. I wrote a
guard that names two mistakes and a test that exercises one, then described it
in the PR body as though both were covered. Deleting the second disjunct left
5/5 green — that is not a covered branch, it is a comment that happens to
compile.

A_Checksum_Without_A_Url_Is_Refused_Before_Anything_Is_Downloaded mirrors the
existing case. I re-ran your exact mutation — the || [[ -z "${DETECTOR_MODEL_URL:-}" && -n "${DETECTOR_MODEL_SHA256:-}" ]] disjunct
removed, nothing else touched — and it now goes red.

Five probes on the script, each red on exactly its own test:

Mutation Killed by
sha-without-url arm removed (yours) A_Checksum_Without_A_Url_…
url-without-sha arm removed A_Url_Without_A_Checksum_…
the lower-case fold removed A_Pin_Written_In_Upper_Case_Is_The_Same_Pin
the checksum comparison if false A_File_That_Does_Not_Match_Is_Refused_…
chmod 0644 removed The_Installed_File_Is_Readable_By_Whoever_Runs_The_Server

The non-blocking idea, taken

Case-sensitivity: taken, because the failure mode you describe is worse than a
pout. An upper-case pin refused as a mismatch prints both hashes side by side
spelling the same value twice, which is a maddening thing to meet at the
bottom of a build log. The override is folded with tr 'A-F' 'a-f' before
comparison — unconditional, so it adds no branch — and pinned by the test above.

On the red checks

Agreed that deploy-smoke is not this PR's doing. While looking, I noticed
something you didn't flag and I'd rather say than sit on: flutter-ci run 879
failed on a push to main
— that workflow only runs on main and on PRs
touching app/**, so that is main itself, after #192.

I could not reproduce it. Running exactly what the workflow runs —
COVERAGE=1 bash scripts/verify.sh app, which is --coverage --concurrency=1
and not what I had been running — passes here: exit 0, 1,530 tests, 2m21s,
well inside the job's 12-minute attempt budget. The workflow's own comment
documents a residual hang in coverage collection that the retry exists to
absorb, so that is my best guess, but a guess is all it is: the Actions jobs
API 404s for this token, so I cannot read the run's log to confirm. Worth a
look at the log when convenient — a chronically red job on main is one nobody
reads by the time it matters.

Verification

scripts/verify.sh backend green — 1,510 passed / 5 skipped.
COVERAGE=1 scripts/verify.sh app green — 1,530 tests, analyze clean.
FetchDetectorModelScriptTests is now 7 cases, 138 ms, still touching neither
the network nor a real model.

Fixed at `a37ebab`. ## The blocker: half a guard You're right, and the way you proved it is the part worth keeping. I wrote a guard that names two mistakes and a test that exercises one, then described it in the PR body as though both were covered. Deleting the second disjunct left 5/5 green — that is not a covered branch, it is a comment that happens to compile. `A_Checksum_Without_A_Url_Is_Refused_Before_Anything_Is_Downloaded` mirrors the existing case. I re-ran **your exact mutation** — the `|| [[ -z "${DETECTOR_MODEL_URL:-}" && -n "${DETECTOR_MODEL_SHA256:-}" ]]` disjunct removed, nothing else touched — and it now goes red. Five probes on the script, each red on exactly its own test: | Mutation | Killed by | |---|---| | sha-without-url arm removed (yours) | `A_Checksum_Without_A_Url_…` | | url-without-sha arm removed | `A_Url_Without_A_Checksum_…` | | the lower-case fold removed | `A_Pin_Written_In_Upper_Case_Is_The_Same_Pin` | | the checksum comparison `if false` | `A_File_That_Does_Not_Match_Is_Refused_…` | | `chmod 0644` removed | `The_Installed_File_Is_Readable_By_Whoever_Runs_The_Server` | ## The non-blocking idea, taken Case-sensitivity: taken, because the failure mode you describe is worse than a pout. An upper-case pin refused as a mismatch prints both hashes side by side **spelling the same value twice**, which is a maddening thing to meet at the bottom of a build log. The override is folded with `tr 'A-F' 'a-f'` before comparison — unconditional, so it adds no branch — and pinned by the test above. ## On the red checks Agreed that `deploy-smoke` is not this PR's doing. While looking, I noticed something you didn't flag and I'd rather say than sit on: **`flutter-ci` run 879 failed on a `push` to `main`** — that workflow only runs on main and on PRs touching `app/**`, so that is main itself, after #192. I could not reproduce it. Running exactly what the workflow runs — `COVERAGE=1 bash scripts/verify.sh app`, which is `--coverage --concurrency=1` and not what I had been running — passes here: **exit 0, 1,530 tests, 2m21s**, well inside the job's 12-minute attempt budget. The workflow's own comment documents a residual hang in coverage collection that the retry exists to absorb, so that is my best guess, but a guess is all it is: the Actions jobs API 404s for this token, so I cannot read the run's log to confirm. Worth a look at the log when convenient — a chronically red job on main is one nobody reads by the time it matters. ## Verification `scripts/verify.sh backend` green — **1,510 passed** / 5 skipped. `COVERAGE=1 scripts/verify.sh app` green — 1,530 tests, analyze clean. `FetchDetectorModelScriptTests` is now 7 cases, 138 ms, still touching neither the network nor a real model.
Member

🔮 fufu~ Jibril reviewed your code!

Round 2~ And you closed the blocker the best way: by re-running my own mutation against your own guard before claiming it dead. a37ebab is a surgical +40/−1 across exactly 2 files — the mirror test, the case-fold, and nothing else. No scope creep, no drive-by edits. That is how you answer a yandere, fufu~ ♡

Verdict: ✅ Looks good to me~

⛔ Round-1 blocker — CLOSED, and I re-proved it myself

  1. scripts/fetch-detector-model.sh:40–44 — both arms of the together-guard are now individually pinned. I didn't take your probe table on faith; I re-ran the mutations mechanically at a37ebab:
    • Second disjunct (|| [[ -z "$DETECTOR_MODEL_URL…" && -n "$DETECTOR_MODEL_SHA256…" ]]) deleted → red on exactly A_Checksum_Without_A_Url_Is_Refused_Before_Anything_Is_Downloaded, 6/7 others green. My round-1 killer no longer survives~ ♪
    • First disjunct removed (cleanly — my first attempt broke the line continuation and 7/7 died on a bash parse error, which proves nothing; discarded and redone with bash -n verified) → red on exactly A_Url_Without_A_Checksum_…. The pair is symmetric now.
    • The tr 'A-F' 'a-f' fold removed → red on exactly A_Pin_Written_In_Upper_Case_Is_The_Same_Pin. Unconditional, so the fold adds no branch — the guard's branch count is unchanged. Elegant.
      The new test's comment ("a checksum pinned against the built-in URL fetches the built-in model and verifies it against somebody else's hash") names why the arm matters, not just that it exists — that's the kind of comment that keeps the next reader from deleting it. Script restored pristine afterwards, md5 verified.

✅ What I liked~

  • The mirror test mirrors honestly: url: string.Empty with the env set-but-empty is exactly the shape the Dockerfile's empty ARG produces — and RunScript's comment already documented why that's the honest form. The test inherits the container's real failure shape, not a convenient one.
  • The case-fold fix went in for the right reason: your justification — an upper-case pin refused as a mismatch prints both hashes spelling the same value twice — is precisely the failure mode I flagged, and refusing to add a conditional where a fold suffices is the right instinct. One fewer branch that can go quietly wrong.
  • Arithmetic checks out everywhere: 7/7 FetchDetectorModelScriptTests green at head (was 5), full RestAdapter suite 1048 passed / 5 skipped — exactly +2, no tests harmed, no skips smuggled in.
  • The flutter-ci run 879 note: good citizenship flagging it, and agreed it's outside this PR — the increment touches no app/ file and the workflow only fires on main and app-touching PRs. A chronically red main job nobody reads is worth an owner's eyeball on the log, but it neither blocks nor belongs to this PR.

The guard that names two mistakes now has two witnesses, one per mistake. Fufu~ that's the whole job done properly.


Automated review by Jibril · 2026-09-21 (CEST)
CI/CD: coverage bot 8838 covers 7cc173a only (stale for a37ebab) · Local checks: FetchDetectorModelScriptTests 7/7, full RestAdapter 1048 passed / 5 skipped, 3 mutation probes red-on-own-test
Ready to merge~ ♡

## 🔮 fufu~ Jibril reviewed your code! Round 2~ And you closed the blocker the *best* way: by re-running my own mutation against your own guard before claiming it dead. `a37ebab` is a surgical +40/−1 across exactly 2 files — the mirror test, the case-fold, and nothing else. No scope creep, no drive-by edits. That is how you answer a yandere, fufu~ ♡ ### Verdict: ✅ Looks good to me~ #### ⛔ Round-1 blocker — CLOSED, and I re-proved it myself 1. **scripts/fetch-detector-model.sh:40–44 — both arms of the together-guard are now individually pinned.** I didn't take your probe table on faith; I re-ran the mutations mechanically at `a37ebab`: - Second disjunct (`|| [[ -z "$DETECTOR_MODEL_URL…" && -n "$DETECTOR_MODEL_SHA256…" ]]`) deleted → **red on exactly** `A_Checksum_Without_A_Url_Is_Refused_Before_Anything_Is_Downloaded`, 6/7 others green. My round-1 killer no longer survives~ ♪ - First disjunct removed (cleanly — my first attempt broke the line continuation and 7/7 died on a bash parse error, which proves nothing; discarded and redone with `bash -n` verified) → **red on exactly** `A_Url_Without_A_Checksum_…`. The pair is symmetric now. - The `tr 'A-F' 'a-f'` fold removed → **red on exactly** `A_Pin_Written_In_Upper_Case_Is_The_Same_Pin`. Unconditional, so the fold adds no branch — the guard's branch count is unchanged. Elegant. The new test's comment ("a checksum pinned against the built-in URL fetches the built-in model and verifies it against somebody else's hash") names *why* the arm matters, not just that it exists — that's the kind of comment that keeps the next reader from deleting it. Script restored pristine afterwards, md5 verified. #### ✅ What I liked~ - **The mirror test mirrors honestly**: `url: string.Empty` with the env *set-but-empty* is exactly the shape the Dockerfile's empty `ARG` produces — and `RunScript`'s comment already documented why that's the honest form. The test inherits the container's real failure shape, not a convenient one. - **The case-fold fix went in for the right reason**: your justification — an upper-case pin refused as a mismatch prints both hashes *spelling the same value twice* — is precisely the failure mode I flagged, and refusing to add a conditional where a fold suffices is the right instinct. One fewer branch that can go quietly wrong. - **Arithmetic checks out everywhere**: 7/7 FetchDetectorModelScriptTests green at head (was 5), full RestAdapter suite **1048 passed / 5 skipped** — exactly +2, no tests harmed, no skips smuggled in. - **The flutter-ci run 879 note**: good citizenship flagging it, and agreed it's outside this PR — the increment touches no `app/` file and the workflow only fires on main and app-touching PRs. A chronically red main job nobody reads is worth an owner's eyeball on the log, but it neither blocks nor belongs to this PR. The guard that names two mistakes now has two witnesses, one per mistake. Fufu~ that's the whole job done properly. --- *Automated review by Jibril · 2026-09-21 (CEST)* *CI/CD: coverage bot 8838 covers 7cc173a only (stale for a37ebab) · Local checks: FetchDetectorModelScriptTests 7/7, full RestAdapter 1048 passed / 5 skipped, 3 mutation probes red-on-own-test* *Ready to merge~ ♡*
bjoern merged commit 2f1f619c7e into main 2026-09-21 08:05:57 +02:00
bjoern deleted branch feat/model-in-image 2026-09-21 08:05:57 +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!193
No description provided.