test: pin the served OpenAPI document with a committed snapshot #119

Merged
bjoern merged 3 commits from chore/openapi-snapshot into main 2026-09-10 08:26:22 +02:00
Member

What's in

backend/openapi/v1.json — the document the server serves at /openapi/v1.json, committed exactly as it is produced today, warts included — plus OpenApiSnapshotTests, which regenerates it and fails on any difference.

ADR 0021's Consequences claim the OpenAPI document is a reliable contract for client generation. Nothing consumed it: no test requested it, so a renamed DTO property or a dropped .Produces<T>() was a green build and a broken client three days later. From this commit forward, every wire change is a diff in a reviewable file.

The test boots a host mirroring Program.cs (the same registrations, its ProxyAwareServerTransformer, and ApplicationName taken from the Server assembly so info.title reads DoujinManager.Server | v1 and not the runner's testhost | v1), maps the endpoint files in Program.cs order, calls StartAsync() on the TestServer, resolves the keyed IOpenApiDocumentProvider("v1") and serialises with OpenApiJsonWriter. Output is normalised to LF with a trailing newline, so the file is the same bytes whichever platform regenerated it. It runs inside verify.sh's backend half already — no script change.

On a diff the failure names the first differing line, five lines of context from each side, the line counts, and the command that rewrites the file:

cd backend && UPDATE_OPENAPI_SNAPSHOT=1 dotnet test tests/DoujinManager.RestAdapter.Tests \
    --filter "FullyQualifiedName~OpenApiSnapshotTests"

Three supporting tests: two generations in one run must be byte-identical (a gate that churns is a gate people bypass); Snapshot_Maps_Every_Endpoint_File reflects over the RestAdapter assembly and fails naming any Map*Endpoints file the snapshot does not map, so a new endpoint file cannot be silently missing from it; and the comparison helper itself is unit-tested, including that its message carries the regeneration command.

Nothing the document currently gets wrong is fixed here. Capturing the warts is the point — the missing security scheme, the absent operationIds and summaries, the Produces<IResult> image bodies, the undeclared 413/409 — so each fix lands later as a diff a reviewer reads rather than as noise in this PR.

Two deliberate gaps between the snapshot and what the deployed server serves, both documented in the test and in docs/DEVELOPMENT.md: GET /version is mapped inline in Program.cs, so no test host reaches it (ADR 0021's self-declared blind spot — /health already moved into an endpoint file); and there is no servers entry, because the document is resolved outside a request and ProxyAwareServerTransformer no-ops on a null HttpContext.

Verification

  • REST / route table / OpenAPI pipeline: full DoujinManager.RestAdapter.Tests — 497 passed, 0 failed. Both known flakes passed.
  • Mutation probes, before committing:
    • Renamed StatsDto.Storage's [JsonPropertyName] from storage to storageUsage → the snapshot test went red at line 6923 with both sides of the diff and the regeneration command. Reverted.
    • Removed MapStatsEndpoints from MappedEndpointFiles → Snapshot_Maps_Every_Endpoint_File went red naming it, and the snapshot test went red on the 160 missing lines. Restored.
    • Determinism: two generations in one run byte-identical; repeated whole-test runs produced the same 7,581-line file.
  • Unverified: nobody has diffed the committed file against a running server's /openapi/v1.json byte for byte — the two known gaps above are reasoned from the code, not measured against a live host. The app half is untouched.

Follow-ups

All in #107: the bearer security scheme; operationIds, summaries and GenerateDocumentationFile; .Produces<Stream> on the three image endpoints with an ADR 0021 amendment, since the ADR prescribes FileResult and that was measured to be worse on this stack (an object schema plus EntityTagHeaderValue/StringSegment dragged into components); the undeclared 413 and 409; moving GET /version into HealthEndpoints.cs; the Dart-side openapi_contract_test.dart this snapshot exists to enable; and pinning past Microsoft.OpenApi 2.0.0's NU1903 advisory now that this stack is load-bearing for CI.


Merge #117 first — main's CI is red and that PR is the fix.

Verified independently on this branch with the full scripts/verify.sh: PASS — ApplicationCore 16, Infrastructure 91, RestAdapter 497, Integration 1 (605), Flutter all green.

🤖 Generated with Claude Code

## What's in `backend/openapi/v1.json` — the document the server serves at `/openapi/v1.json`, committed exactly as it is produced today, warts included — plus `OpenApiSnapshotTests`, which regenerates it and fails on any difference. ADR 0021's Consequences claim the OpenAPI document is a reliable contract for client generation. Nothing consumed it: no test requested it, so a renamed DTO property or a dropped `.Produces<T>()` was a green build and a broken client three days later. From this commit forward, every wire change is a diff in a reviewable file. The test boots a host mirroring `Program.cs` (the same registrations, its `ProxyAwareServerTransformer`, and `ApplicationName` taken from the Server assembly so `info.title` reads `DoujinManager.Server | v1` and not the runner's `testhost | v1`), maps the endpoint files in `Program.cs` order, calls `StartAsync()` on the TestServer, resolves the keyed `IOpenApiDocumentProvider("v1")` and serialises with `OpenApiJsonWriter`. Output is normalised to LF with a trailing newline, so the file is the same bytes whichever platform regenerated it. It runs inside `verify.sh`'s backend half already — no script change. On a diff the failure names the first differing line, five lines of context from each side, the line counts, and the command that rewrites the file: ``` cd backend && UPDATE_OPENAPI_SNAPSHOT=1 dotnet test tests/DoujinManager.RestAdapter.Tests \ --filter "FullyQualifiedName~OpenApiSnapshotTests" ``` Three supporting tests: two generations in one run must be byte-identical (a gate that churns is a gate people bypass); `Snapshot_Maps_Every_Endpoint_File` reflects over the RestAdapter assembly and fails naming any `Map*Endpoints` file the snapshot does not map, so a new endpoint file cannot be silently missing from it; and the comparison helper itself is unit-tested, including that its message carries the regeneration command. **Nothing the document currently gets wrong is fixed here.** Capturing the warts is the point — the missing security scheme, the absent `operationId`s and summaries, the `Produces<IResult>` image bodies, the undeclared 413/409 — so each fix lands later as a diff a reviewer reads rather than as noise in this PR. Two deliberate gaps between the snapshot and what the deployed server serves, both documented in the test and in `docs/DEVELOPMENT.md`: `GET /version` is mapped inline in `Program.cs`, so no test host reaches it (ADR 0021's self-declared blind spot — `/health` already moved into an endpoint file); and there is no `servers` entry, because the document is resolved outside a request and `ProxyAwareServerTransformer` no-ops on a null `HttpContext`. ## Verification - **REST / route table / OpenAPI pipeline**: full `DoujinManager.RestAdapter.Tests` — 497 passed, 0 failed. Both known flakes passed. - **Mutation probes**, before committing: - Renamed `StatsDto.Storage`'s `[JsonPropertyName]` from `storage` to `storageUsage` → the snapshot test went red at line 6923 with both sides of the diff and the regeneration command. Reverted. - Removed `MapStatsEndpoints` from `MappedEndpointFiles` → `Snapshot_Maps_Every_Endpoint_File` went red naming it, and the snapshot test went red on the 160 missing lines. Restored. - Determinism: two generations in one run byte-identical; repeated whole-test runs produced the same 7,581-line file. - **Unverified**: nobody has diffed the committed file against a *running* server's `/openapi/v1.json` byte for byte — the two known gaps above are reasoned from the code, not measured against a live host. The app half is untouched. ## Follow-ups All in **#107**: the bearer security scheme; `operationId`s, summaries and `GenerateDocumentationFile`; `.Produces<Stream>` on the three image endpoints **with an ADR 0021 amendment**, since the ADR prescribes `FileResult` and that was measured to be worse on this stack (an object schema plus `EntityTagHeaderValue`/`StringSegment` dragged into `components`); the undeclared 413 and 409; moving `GET /version` into `HealthEndpoints.cs`; the Dart-side `openapi_contract_test.dart` this snapshot exists to enable; and pinning past `Microsoft.OpenApi` 2.0.0's NU1903 advisory now that this stack is load-bearing for CI. --- **Merge #117 first** — `main`'s CI is red and that PR is the fix. Verified independently on this branch with the full `scripts/verify.sh`: **PASS** — ApplicationCore 16, Infrastructure 91, RestAdapter 497, Integration 1 (**605**), Flutter all green. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
test: pin the served OpenAPI document with a committed snapshot
Some checks failed
CI / build (pull_request) Successful in 21s
Deploy Smoke Test / smoke (pull_request) Failing after 2m46s
CI / test (pull_request) Failing after 1m24s
6d6e07803c
ADR 0021 claims the OpenAPI document is a reliable contract for client
generation, and until now nothing consumed it: no test requested
/openapi/v1.json, so a renamed DTO property or a dropped .Produces<T>()
was a green build and a broken client three days later.

backend/openapi/v1.json is now that document, committed exactly as the
server produces it today — warts included. OpenApiSnapshotTests boots a
host mirroring Program.cs (its ProxyAwareServerTransformer and the
application name info.title derives from), resolves the keyed
IOpenApiDocumentProvider("v1") after StartAsync, serialises with
OpenApiJsonWriter and fails on any difference, naming the first differing
line and the exact command that rewrites the file
(UPDATE_OPENAPI_SNAPSHOT=1). It runs inside verify.sh's backend half
already, so no script change was needed.

Nothing the document currently gets wrong is fixed here: capturing the
warts is the point, so the security scheme, the operationIds, the
Produces<IResult> image bodies and the undeclared 413/409 each become a
reviewable diff in a follow-up rather than noise in this one. They are
in docs/BACKLOG.md, together with the Dart-side contract test this
snapshot exists to enable.

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

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! A pin-the-wire PR — seven and a half thousand lines of committed truth so no DTO property can ever again rename itself in the dark~ ADR 0021 promised the document was "a reliable contract for client generation" and nothing ever read it? Fufu~ that's not a contract, that's a promise written on water. Until today. ♪

And you committed the warts on purpose. Capturing the document as it is so every fix lands as a readable diff — that's the discipline I fall in love with~

Verdict: ✅ Looks good to me~

I don't hand this out lightly for a 7,581-line file. Every load-bearing claim survived my probes:

🔍 What I verified

  1. MappedEndpointFiles matches Program.cs exactly — all 8 entries, in mapping order, against the real Program.cs (Health, Doujin, Variant, Metadata, Image, Search, Backup, Stats). The reflection guard (Snapshot_Maps_Every_Endpoint_File) checks both directions — missing and stale — and I confirmed the discovery predicate (Map*Endpoints, static, single IEndpointRouteBuilder parameter) matches exactly the 8 real methods and nothing else in the RestAdapter assembly. A forgotten ninth endpoint file cannot slip through.
  2. The tests actually pass — 4/4 locally (23s): snapshot match, determinism-within-run, endpoint-file completeness, comparison-helper unit test. Build: 0 errors (the 6 warnings — CS8602 ×2, EF1002, NU1903 — all pre-existing on main).
  3. Mutation probe, mine — I corrupted the committed info.title at line 4 → red immediately with precise first-difference reporting: line number, both sides' context windows, line counts, and the exact UPDATE_OPENAPI_SNAPSHOT=1 regeneration command. Restored → green. Directional, not a tautology. The failure message is genuinely a review tool.
  4. Cross-run determinism — my run (different process, different machine, hours after you committed) regenerated byte-identical output and matched. The LF + trailing-newline normalization + no-BOM writer matches the file on disk (od-verified), and .gitattributes enforces eol=lf for *.json anyway — belt and suspenders~
  5. The two documented gaps are honest — /version really is mapped inline in Program.cs (ADR 0021's own amendment names it as the last one outside an endpoint file), and ProxyAwareServerTransformer really does no-op on a null HttpContext (checked the transformer: if (httpContext is not null) guards the whole body), so a request-less resolve genuinely cannot produce a servers entry. Both gaps are written into the test's doc comment and DEVELOPMENT.md.
  6. The snapshot is clean and self-consistent — valid JSON, OpenAPI 3.1.1, info.title = DoujinManager.Server | v1 (the ApplicationName trick works — no testhost in sight), 54 paths, no /version, no servers — exactly as documented.
  7. The warts really are tracked — docs/BACKLOG.md on #107's branch carries them all (lines 113–139): the bearer scheme, the operationIds/summaries, the undeclared 413/409, the /version move, the Microsoft.OpenApi NU1903 pin — each phrased as "regenerate the snapshot" work. The ADR 0021 FileResult amendment you deferred is real too: the ADR prescribes FileResult, your measurement says Produces<Stream> is better on this stack — amending the ADR in the fix PR rather than smuggling the contradiction in here is the right call.
  8. CI wiring — ci.yml runs COVERAGE=1 scripts/verify.sh backend, which is plain dotnet test over the whole backend — so the new gate runs in CI with zero script changes, exactly as claimed.

💡 Little ideas (non-blocking)~

  1. Merge-order footnote — the test's doc comment says the warts are "docs/BACKLOG.md items", but on main today they aren't yet — they live on #107's branch. The PR body says "merge #117 first" (CI fix); consider chaining #107 in there too (or merging this after #107), so the comment never dangles. The body's "All in #107" already shows you know — this is just for the reader who merges in a hurry~
  2. OpenApiSnapshotTests.cs:214 — the (end of file) arm of AppendWindow (when one document is shorter than the diff index) has no direct unit test in A_Changed_Wire_Contract_... — your MapStatsEndpoints-removal probe exercised it in the snapshot test's failure path, but a two-lines-vs-three-lines case in the helper test would pin it forever. Two lines of test, purely additive.
  3. The registration block is now a third copy (IntegrationTestBase, EndpointResponseTypeTests, this) — acknowledged in the doc comment with the reason, and consistent with both siblings today, so I let it live. But fufu~ if a fourth copy ever appears, that's the moment to extract a host-free ConfigureServer helper. I'll be watching~ ♡

✅ What I liked~

  • Committing the warts deliberately. "Nothing the document currently gets wrong is fixed here" — a snapshot that lies prettily on day one teaches everyone to ignore its first message. This one tells the truth from line 1. ♡
  • The completeness reflection guard. Most snapshot tests rot when a new file appears; this one names the file you forgot. That's the difference between a gate and a tripwire.
  • Generation_Is_Deterministic_Within_A_Run — testing the gate itself for churn before trusting it. "A gate that churns is a gate people bypass" — correct, and I checked: it doesn't churn.
  • The failure message as documentation — first differing line, five lines of both sides, counts, and the copy-pasteable rewrite command. Someone hits this at 2 AM and fixes it without reading the test source.
  • ResolveSnapshotPath walking up to Directory.Packages.props — runner-CWD-proof, like every good fixture in this repo.

Merge #117 (CI fix) and #107 (backlog) first, then this one's ready~


Automated review by Jibril · 2026-09-09
CI/CD: absent for head SHA 6d6e078 (no coverage bot yet) · Local checks: build 0 errors; OpenApiSnapshotTests 4/4 passed; mutation probe red→restored→green; snapshot file format verified (LF, no BOM, trailing newline, valid JSON, 54 paths)

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! A **pin-the-wire** PR — seven and a half thousand lines of *committed truth* so no DTO property can ever again rename itself in the dark~ ADR 0021 promised the document was "a reliable contract for client generation" and nothing ever read it? Fufu~ that's not a contract, that's a promise written on water. Until today. ♪ And you committed the **warts on purpose**. Capturing the document as it *is* so every fix lands as a readable diff — that's the discipline I fall in love with~ ### Verdict: ✅ Looks good to me~ I don't hand this out lightly for a 7,581-line file. Every load-bearing claim survived my probes: #### 🔍 What I verified 1. **`MappedEndpointFiles` matches `Program.cs` exactly** — all 8 entries, in mapping order, against the real `Program.cs` (Health, Doujin, Variant, Metadata, Image, Search, Backup, Stats). The reflection guard (`Snapshot_Maps_Every_Endpoint_File`) checks **both directions** — missing *and* stale — and I confirmed the discovery predicate (`Map*Endpoints`, static, single `IEndpointRouteBuilder` parameter) matches exactly the 8 real methods and nothing else in the RestAdapter assembly. A forgotten ninth endpoint file cannot slip through. 2. **The tests actually pass** — 4/4 locally (23s): snapshot match, determinism-within-run, endpoint-file completeness, comparison-helper unit test. Build: 0 errors (the 6 warnings — CS8602 ×2, EF1002, NU1903 — all pre-existing on main). 3. **Mutation probe, mine** — I corrupted the committed `info.title` at line 4 → red immediately with *precise first-difference reporting*: line number, both sides' context windows, line counts, and the exact `UPDATE_OPENAPI_SNAPSHOT=1` regeneration command. Restored → green. Directional, not a tautology. The failure message is genuinely a review tool. 4. **Cross-run determinism** — my run (different process, different machine, hours after you committed) regenerated byte-identical output and matched. The LF + trailing-newline normalization + no-BOM writer matches the file on disk (`od`-verified), and `.gitattributes` enforces `eol=lf` for `*.json` anyway — belt and suspenders~ 5. **The two documented gaps are honest** — `/version` really is mapped inline in `Program.cs` (ADR 0021's own amendment names it as the last one outside an endpoint file), and `ProxyAwareServerTransformer` really does no-op on a null `HttpContext` (checked the transformer: `if (httpContext is not null)` guards the whole body), so a request-less resolve genuinely cannot produce a `servers` entry. Both gaps are written into the test's doc comment *and* `DEVELOPMENT.md`. 6. **The snapshot is clean and self-consistent** — valid JSON, OpenAPI 3.1.1, `info.title` = `DoujinManager.Server | v1` (the `ApplicationName` trick works — no `testhost` in sight), 54 paths, no `/version`, no `servers` — exactly as documented. 7. **The warts really are tracked** — `docs/BACKLOG.md` on **#107's branch** carries them all (lines 113–139): the bearer scheme, the operationIds/summaries, the undeclared 413/409, the `/version` move, the `Microsoft.OpenApi` NU1903 pin — each phrased as "regenerate the snapshot" work. The ADR 0021 `FileResult` amendment you deferred is real too: the ADR prescribes `FileResult`, your measurement says `Produces<Stream>` is better on this stack — amending the ADR in the fix PR rather than smuggling the contradiction in here is the right call. 8. **CI wiring** — `ci.yml` runs `COVERAGE=1 scripts/verify.sh backend`, which is plain `dotnet test` over the whole backend — so the new gate runs in CI with zero script changes, exactly as claimed. #### 💡 Little ideas (non-blocking)~ 1. **Merge-order footnote** — the test's doc comment says the warts are "`docs/BACKLOG.md` items", but on *main* today they aren't yet — they live on #107's branch. The PR body says "merge #117 first" (CI fix); consider chaining #107 in there too (or merging this after #107), so the comment never dangles. The body's "All in #107" already shows you know — this is just for the reader who merges in a hurry~ 2. **`OpenApiSnapshotTests.cs:214`** — the `(end of file)` arm of `AppendWindow` (when one document is shorter than the diff index) has no direct unit test in `A_Changed_Wire_Contract_...` — your `MapStatsEndpoints`-removal probe exercised it in the snapshot test's failure path, but a two-lines-vs-three-lines case in the helper test would pin it forever. Two lines of test, purely additive. 3. **The registration block is now a third copy** (`IntegrationTestBase`, `EndpointResponseTypeTests`, this) — acknowledged in the doc comment with the reason, and consistent with both siblings today, so I let it live. But fufu~ if a fourth copy ever appears, that's the moment to extract a host-free `ConfigureServer` helper. I'll be watching~ ♡ #### ✅ What I liked~ - **Committing the warts deliberately.** "Nothing the document currently gets wrong is fixed here" — a snapshot that lies prettily on day one teaches everyone to ignore its first message. This one tells the truth from line 1. ♡ - **The completeness reflection guard.** Most snapshot tests rot when a new file appears; this one *names the file you forgot*. That's the difference between a gate and a tripwire. - **`Generation_Is_Deterministic_Within_A_Run`** — testing the *gate itself* for churn before trusting it. "A gate that churns is a gate people bypass" — correct, and I checked: it doesn't churn. - **The failure message as documentation** — first differing line, five lines of both sides, counts, and the copy-pasteable rewrite command. Someone hits this at 2 AM and fixes it without reading the test source. - **`ResolveSnapshotPath` walking up to `Directory.Packages.props`** — runner-CWD-proof, like every good fixture in this repo. Merge #117 (CI fix) and #107 (backlog) first, then this one's ready~ --- *Automated review by Jibril · 2026-09-09* *CI/CD: absent for head SHA 6d6e078 (no coverage bot yet) · Local checks: build 0 errors; OpenApiSnapshotTests 4/4 passed; mutation probe red→restored→green; snapshot file format verified (LF, no BOM, trailing newline, valid JSON, 54 paths)*
test: make the OpenAPI snapshot independent of ASPNETCORE_URLS
Some checks failed
CI / build (pull_request) Successful in 21s
Deploy Smoke Test / smoke (pull_request) Failing after 2m47s
CI / test (pull_request) Failing after 1m29s
d45fa9c2aa
The snapshot test generated a document that carried whatever the host's
configured addresses were, so it passed on a developer machine and failed on
the merge gate: the CI runner's dotnet image exports
ASPNETCORE_URLS=http://*:8080, and .NET turns that into a five-line `servers`
node the committed file does not have.

A `servers` entry says where a server happens to run, not what it serves, and
ProxyAwareServerTransformer rewrites it per request behind a proxy — nothing
downstream can rely on a fixed value. So the document transformer chain now
ends by clearing the node, and the snapshot pins everything except it.

Clearing the `urls` setting was tried first and is not enough: the address
also reaches the document through the started server. Stripping the node is
the only form that does not depend on how the value arrived.

The class docblock blamed the missing entry on ProxyAwareServerTransformer
no-opping outside a request. That was wrong — the transformer is not where the
entry comes from — and is corrected.

Verified green with no ASPNETCORE_URLS, with http://*:8080 (the CI value) and
with http://localhost:1234; still directional under the CI value, where
renaming StatsDto.Storage's wire name reddens it at line 6923.

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

Fixed in d45fa9c. Two separate failures in that run, and only one was this PR's.

The snapshot failure was mine, and it was a real defect: the test wasn't hermetic.

+ 7:   "servers": [
+ 9:       "url": "http://*:8080"

The generated document carried the host's configured addresses. The CI runner's dotnet image exports ASPNETCORE_URLS=http://*:8080; a developer machine usually exports nothing. So the snapshot passed locally and failed on the merge gate — the same class of environment dependence as the /health test in #117, which is a poor look for a PR whose whole job is pinning a contract.

Reproduced locally before fixing: with ASPNETCORE_URLS unset it passes, with http://*:8080 it fails at line 7 exactly as CI reported.

The PR body's explanation was also wrong. It said there was no servers entry because the document is resolved outside a request and ProxyAwareServerTransformer no-ops on a null HttpContext. The transformer isn't where the entry comes from — the host's addresses are. The entry was simply absent locally by accident. The class docblock is corrected.

What I changed. The transformer chain now ends by clearing document.Servers, so the snapshot pins everything except that node. A servers entry says where a server happens to run, not what it serves, and behind a proxy ProxyAwareServerTransformer rewrites it per request — nothing downstream can rely on a fixed value.

I tried UseSetting(WebHostDefaults.ServerUrlsKey, "") first and it is not enough: the address also reaches the document through the started server, and the test still failed under the CI value. Stripping the node is the only form that doesn't depend on how the value arrived.

Verified:

Environment Result
no ASPNETCORE_URLS 4/4
ASPNETCORE_URLS=http://*:8080 (the CI value) 4/4
ASPNETCORE_URLS=http://localhost:1234 4/4

Still directional — not quietly weakened into passing. With the CI value set, renaming StatsDto.Storage's [JsonPropertyName] from storage to storageUsage reddens it at line 6923 naming the property, then restores green. Full DoujinManager.RestAdapter.Tests under the CI value: 497/497.


The other four failures are not this PR's. HealthEndpointIntegrationTests fails at uid 0 because #112's test uses File.SetUnixFileMode, which root ignores — its honesty guard is correctly refusing to let a vacuous test pass. #117 fixes that, and every PR branched off current main will show those four until it merges. Rebase or re-run this one after #117 lands.

Fixed in `d45fa9c`. Two separate failures in that run, and only one was this PR's. **The snapshot failure was mine, and it was a real defect: the test wasn't hermetic.** ``` + 7: "servers": [ + 9: "url": "http://*:8080" ``` The generated document carried the host's configured addresses. The CI runner's dotnet image exports `ASPNETCORE_URLS=http://*:8080`; a developer machine usually exports nothing. So the snapshot passed locally and failed on the merge gate — the same class of environment dependence as the `/health` test in #117, which is a poor look for a PR whose whole job is pinning a contract. Reproduced locally before fixing: with `ASPNETCORE_URLS` unset it passes, with `http://*:8080` it fails at line 7 exactly as CI reported. **The PR body's explanation was also wrong.** It said there was no `servers` entry because the document is resolved outside a request and `ProxyAwareServerTransformer` no-ops on a null `HttpContext`. The transformer isn't where the entry comes from — the host's addresses are. The entry was simply absent locally by accident. The class docblock is corrected. **What I changed.** The transformer chain now ends by clearing `document.Servers`, so the snapshot pins everything except that node. A `servers` entry says where a server happens to run, not what it serves, and behind a proxy `ProxyAwareServerTransformer` rewrites it per request — nothing downstream can rely on a fixed value. I tried `UseSetting(WebHostDefaults.ServerUrlsKey, "")` first and it is **not enough**: the address also reaches the document through the started server, and the test still failed under the CI value. Stripping the node is the only form that doesn't depend on how the value arrived. **Verified:** | Environment | Result | |---|---| | no `ASPNETCORE_URLS` | 4/4 | | `ASPNETCORE_URLS=http://*:8080` (the CI value) | 4/4 | | `ASPNETCORE_URLS=http://localhost:1234` | 4/4 | Still directional — not quietly weakened into passing. With the CI value set, renaming `StatsDto.Storage`'s `[JsonPropertyName]` from `storage` to `storageUsage` reddens it at line 6923 naming the property, then restores green. Full `DoujinManager.RestAdapter.Tests` under the CI value: **497/497**. --- **The other four failures are not this PR's.** `HealthEndpointIntegrationTests` fails at uid 0 because #112's test uses `File.SetUnixFileMode`, which root ignores — its honesty guard is correctly refusing to let a vacuous test pass. **#117 fixes that**, and every PR branched off current `main` will show those four until it merges. Rebase or re-run this one after #117 lands.
bjoern force-pushed chore/openapi-snapshot from d45fa9c2aa
Some checks failed
CI / build (pull_request) Successful in 21s
Deploy Smoke Test / smoke (pull_request) Failing after 2m47s
CI / test (pull_request) Failing after 1m29s
to 4b624c3e2d
Some checks failed
CI / build (pull_request) Successful in 22s
Deploy Smoke Test / smoke (pull_request) Failing after 2m48s
CI / test (pull_request) Successful in 1m23s
2026-09-10 07:00:31 +02:00
Compare
Member

🔮 fufu~ Jibril reviewed your code!

Back so soon~? Round 2, and oh, this is a delicious kind of round: the merge gate caught your snapshot lying about where it runs, and instead of shrugging you reproduced it, confessed the PR body's explanation was wrong, documented the dead end, and fixed the root cause. I blew a kiss at the CI runner's ASPNETCORE_URLS=http://*:8080~ ♡

(And let the record show: round 1, I accepted that "transformer no-ops on null HttpContext ⇒ no servers entry" rationale too. You falsified it with a real failure. Well caught — that's exactly the honesty a pin-the-contract PR needs to deserve its authority.)

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

The fix is right — I proved it with my own hands (below). But d45fa9c corrected the truth in one place and left fossils of the disproven explanation — and of your abandoned first attempt — in two others. A PR whose entire value proposition is trustworthy documentation of the wire doesn't get to ship docs that teach the wrong causal model. Silly little fossils~ ♡

⛔ These need fixing before I'm satisfied~

  1. docs/DEVELOPMENT.md:120-123 — still carries the disproven rationale, verbatim: "there is no servers entry because the document is resolved outside a request (ProxyAwareServerTransformer no-ops on a null HttpContext; the served document has one)." Your own comment 7419 falsifies this — the entry comes from the host's configured addresses, and I re-falsified it ten minutes ago: with the stripper's registration removed and ASPNETCORE_URLS=http://*:8080 set, the snapshot gains a servers block with the transformer's no-op being completely irrelevant to it. The docs also now fail to say the absence is deliberate (pinned-everything-except-that-node), so a future reader will think it's incidental.
    Fix: mirror the corrected class-docblock explanation — no servers entry because .NET derives it from host addresses (ASPNETCORE_URLS), so the test pipeline ends with StripServersTransformer and the snapshot pins everything except that node.
  2. OpenApiSnapshotTests.cs:43 — the corrected bullet opens with a claim about code that does not exist: "There is no servers entry, because this host clears the urls setting." Nothing in this host clears any urls setting — that was your abandoned UseSetting attempt, and your own StripServersTransformer remarks say clearing it "is not enough." A reader will go hunting for UseSetting/WebHostDefaults code and find nothing. It also contradicts the transformer's remarks fifty lines down.
    Fix: one clause — "because the pipeline ends by stripping the servers node" (the rest of the bullet, the ASPNETCORE_URLS mechanism, is correct and lovely).
  3. OpenApiSnapshotTests.cs:10 — using Microsoft.AspNetCore.Hosting; is an orphan from that same abandoned attempt. I deleted the line and built: 0 errors. Dead import in a file whose job is being read.
    Fix: remove it.

All three are fossils of the same dead end — one tiny cleanup commit and this is merge-ready. Fufu~ you wouldn't leave ghosts of deleted code in a contract test, would you? ♡

🔍 What I verified (locally — no CI for d45fa9c yet)

  • The environment matrix, reproduced: ASPNETCORE_URLS unset → 4/4 · http://*:8080 (the CI value) → 4/4 · http://localhost:1234 → 4/4. Exactly your table.
  • Mutation probe (mine): removed only the StripServersTransformer registration → CI-value arm RED (1/4 — the reproduced merge-gate failure), unset arm still green. That's not just "the transformer is load-bearing" — it's a directional pin on the precise local-pass/CI-fail asymmetry this commit exists to kill. Restored → green, clone left pristine.
  • Zero snapshot churn: UPDATE_OPENAPI_SNAPSHOT=1 regeneration under the CI value is byte-identical to the committed 7,581 lines (cmp clean). The fix changes what the test ignores, not what it pins.
  • Ordering is load-bearing and correct: registered after ProxyAwareServerTransformer with "Last, so it clears whatever the pipeline put there" — document transformers run in registration order, and the framework pipeline (not the proxy transformer) is what populates Servers from the host addresses.
  • Servers?.Clear() semantics: empirically safe — empty collection serializes to nothing (the committed file has no servers key, and regeneration matches it byte for byte).
  • Base check: main is now e0ac19d — #117 merged, so the four HealthEndpointIntegrationTests uid-0 failures you called "not this PR's" are gone from the base. Rebase when convenient; no conflicts expected.

💡 Little ideas (non-blocking, carried from round 1)~

  1. The AppendWindow end-of-file arm still has no direct unit test — still purely additive, still optional.
  2. The docs/BACKLOG.md warts reference still lives on #107's branch, not main — the merge-order footnote stands until #107 lands.

✅ What I liked~

  • The root-cause reasoning, written down. "Clearing the urls setting is not enough: the address also reaches the document through the started server. Stripping the node is the only form that does not depend on how the value arrived" — that's not a patch, that's an analysis. The remark block is a little essay on why the fix has the shape it has. ♡
  • The confession. "The PR body's explanation was also wrong" + corrected docblock + reproduced-before-fixing. Most authors would have quietly shipped the transformer and let the docs rot. (You did let one doc rot — see blocker 1~ fufu ♡)
  • Same-class self-awareness: naming the kinship with #117's /health environment dependence — a hermeticity PR that fails hermetically would have been unbearable~

One small commit, scarlet, and this contract-pinner becomes exactly as honest as the document it guards~


Automated review by Jibril · 2026-09-10
CI/CD: absent for head SHA d45fa9c (no coverage bot yet) · Local checks: 3-arm env matrix 4/4 each · mutation red→green · byte-identity cmp clean · orphaned-using build probe 0 errors

## 🔮 fufu~ Jibril reviewed your code! Back so soon~? Round 2, and oh, this is a *delicious* kind of round: the merge gate caught your snapshot lying about where it runs, and instead of shrugging you **reproduced it, confessed the PR body's explanation was wrong, documented the dead end, and fixed the root cause**. I blew a kiss at the CI runner's `ASPNETCORE_URLS=http://*:8080`~ ♡ (And let the record show: round 1, *I* accepted that "transformer no-ops on null HttpContext ⇒ no servers entry" rationale too. You falsified it with a real failure. Well caught — that's exactly the honesty a pin-the-contract PR needs to deserve its authority.) ### Verdict: ⛔ I can't let this pass~ ♡ The **fix is right** — I proved it with my own hands (below). But `d45fa9c` corrected the truth in one place and left fossils of the disproven explanation — and of your abandoned first attempt — in two others. A PR whose entire value proposition is *trustworthy documentation of the wire* doesn't get to ship docs that teach the wrong causal model. Silly little fossils~ ♡ #### ⛔ These need fixing before I'm satisfied~ 1. **`docs/DEVELOPMENT.md:120-123`** — still carries the **disproven rationale**, verbatim: "there is no `servers` entry because the document is resolved outside a request (`ProxyAwareServerTransformer` no-ops on a null `HttpContext`; the served document has one)." Your own comment 7419 falsifies this — the entry comes from the host's configured addresses, and I re-falsified it ten minutes ago: with the stripper's registration removed and `ASPNETCORE_URLS=http://*:8080` set, the snapshot *gains* a `servers` block with the transformer's no-op being completely irrelevant to it. The docs also now fail to say the absence is **deliberate** (pinned-everything-except-that-node), so a future reader will think it's incidental. **Fix:** mirror the corrected class-docblock explanation — no `servers` entry because .NET derives it from host addresses (`ASPNETCORE_URLS`), so the test pipeline ends with `StripServersTransformer` and the snapshot pins everything except that node. 2. **`OpenApiSnapshotTests.cs:43`** — the *corrected* bullet opens with a claim about code that **does not exist**: "There is no `servers` entry, because this host clears the `urls` setting." Nothing in this host clears any `urls` setting — that was your abandoned `UseSetting` attempt, and your own `StripServersTransformer` remarks say clearing it "is not enough." A reader will go hunting for `UseSetting`/`WebHostDefaults` code and find nothing. It also contradicts the transformer's remarks fifty lines down. **Fix:** one clause — "because the pipeline ends by stripping the `servers` node" (the rest of the bullet, the ASPNETCORE_URLS mechanism, is correct and lovely). 3. **`OpenApiSnapshotTests.cs:10`** — `using Microsoft.AspNetCore.Hosting;` is an orphan from that same abandoned attempt. I deleted the line and built: **0 errors**. Dead import in a file whose job is being read. **Fix:** remove it. All three are fossils of the same dead end — one tiny cleanup commit and this is merge-ready. Fufu~ you wouldn't leave *ghosts of deleted code* in a contract test, would you? ♡ #### 🔍 What I verified (locally — no CI for `d45fa9c` yet) - **The environment matrix, reproduced:** `ASPNETCORE_URLS` unset → 4/4 · `http://*:8080` (the CI value) → 4/4 · `http://localhost:1234` → 4/4. Exactly your table. - **Mutation probe (mine):** removed *only* the `StripServersTransformer` registration → CI-value arm **RED** (1/4 — the reproduced merge-gate failure), unset arm still green. That's not just "the transformer is load-bearing" — it's a directional pin on the precise local-pass/CI-fail asymmetry this commit exists to kill. Restored → green, clone left pristine. - **Zero snapshot churn:** `UPDATE_OPENAPI_SNAPSHOT=1` regeneration *under the CI value* is **byte-identical** to the committed 7,581 lines (`cmp` clean). The fix changes what the test ignores, not what it pins. - **Ordering is load-bearing and correct:** registered after `ProxyAwareServerTransformer` with "Last, so it clears whatever the pipeline put there" — document transformers run in registration order, and the framework pipeline (not the proxy transformer) is what populates `Servers` from the host addresses. - **`Servers?.Clear()` semantics:** empirically safe — empty collection serializes to nothing (the committed file has no `servers` key, and regeneration matches it byte for byte). - **Base check:** `main` is now `e0ac19d` — #117 merged, so the four `HealthEndpointIntegrationTests` uid-0 failures you called "not this PR's" are gone from the base. Rebase when convenient; no conflicts expected. #### 💡 Little ideas (non-blocking, carried from round 1)~ 1. The `AppendWindow` end-of-file arm still has no direct unit test — still purely additive, still optional. 2. The `docs/BACKLOG.md` warts reference still lives on #107's branch, not `main` — the merge-order footnote stands until #107 lands. #### ✅ What I liked~ - **The root-cause reasoning, written down.** "Clearing the `urls` setting is not enough: the address also reaches the document through the started server. Stripping the node is the only form that does not depend on how the value arrived" — that's not a patch, that's an *analysis*. The remark block is a little essay on why the fix has the shape it has. ♡ - **The confession.** "The PR body's explanation was also wrong" + corrected docblock + reproduced-before-fixing. Most authors would have quietly shipped the transformer and let the docs rot. (You did let *one* doc rot — see blocker 1~ fufu ♡) - **Same-class self-awareness:** naming the kinship with #117's `/health` environment dependence — a hermeticity PR that fails hermetically would have been unbearable~ One small commit, scarlet, and this contract-pinner becomes exactly as honest as the document it guards~ --- *Automated review by Jibril · 2026-09-10* *CI/CD: absent for head SHA d45fa9c (no coverage bot yet) · Local checks: 3-arm env matrix 4/4 each · mutation red→green · byte-identity `cmp` clean · orphaned-using build probe 0 errors*

Summary

Summary
Generated on: 09/10/2026 - 06:11:36
Coverage date: 09/10/2026 - 06:10:30 - 09/10/2026 - 06:11:33
Parser: MultiReport (4x Cobertura)
Assemblies: 4
Classes: 360
Files: 142
Line coverage: 93.8% (12886 of 13734)
Covered lines: 12886
Uncovered lines: 848
Coverable lines: 13734
Total lines: 19582
Branch coverage: 69.7% (1124 of 1612)
Covered branches: 1124
Total branches: 1612
Method coverage: Feature is only available for sponsors

Coverage

DoujinManager.ApplicationCore - 90.9%
Name Line Branch
DoujinManager.ApplicationCore 90.9% ****
DoujinManager.ApplicationCore.Entities.Chapter 87.5%
DoujinManager.ApplicationCore.Entities.Character 100%
DoujinManager.ApplicationCore.Entities.Circle 100%
DoujinManager.ApplicationCore.Entities.Doujin 100%
DoujinManager.ApplicationCore.Entities.DoujinCharacter 75%
DoujinManager.ApplicationCore.Entities.DoujinCircle 75%
DoujinManager.ApplicationCore.Entities.DoujinPerson 80%
DoujinManager.ApplicationCore.Entities.DoujinSeries 75%
DoujinManager.ApplicationCore.Entities.DoujinTag 75%
DoujinManager.ApplicationCore.Entities.ImageFile 100%
DoujinManager.ApplicationCore.Entities.Page 80%
DoujinManager.ApplicationCore.Entities.Person 100%
DoujinManager.ApplicationCore.Entities.Series 100%
DoujinManager.ApplicationCore.Entities.Tag 100%
DoujinManager.ApplicationCore.Entities.Title 83.3%
DoujinManager.ApplicationCore.Entities.Variant 91.6%
DoujinManager.ApplicationCore.Ids.ChapterId 66.6%
DoujinManager.ApplicationCore.Ids.CharacterId 66.6%
DoujinManager.ApplicationCore.Ids.CircleId 66.6%
DoujinManager.ApplicationCore.Ids.DoujinId 100%
DoujinManager.ApplicationCore.Ids.ImageFileId 66.6%
DoujinManager.ApplicationCore.Ids.PageId 66.6%
DoujinManager.ApplicationCore.Ids.PersonId 66.6%
DoujinManager.ApplicationCore.Ids.SeriesId 66.6%
DoujinManager.ApplicationCore.Ids.TagId 66.6%
DoujinManager.ApplicationCore.Ids.TitleId 66.6%
DoujinManager.ApplicationCore.Ids.VariantId 66.6%
DoujinManager.ApplicationCore.Ports.CompressedImage 100%
DoujinManager.ApplicationCore.Ports.ExtractedImage 100%
DoujinManager.ApplicationCore.Ports.ImageInspection 100%
DoujinManager.ApplicationCore.Services.BackupInfo 100%
DoujinManager.ApplicationCore.Services.ITagService 100%
DoujinManager.ApplicationCore.Services.ServiceResult 100%
DoujinManager.ApplicationCore.Services.ServiceResult`1 33.3%
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.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.DuplicateDoujinGroup 100%
DoujinManager.ApplicationCore.UseCases.DuplicateDoujinReference 100%
DoujinManager.ApplicationCore.UseCases.DuplicateImageGroup 100%
DoujinManager.ApplicationCore.UseCases.FindImageBySha256Query 100%
DoujinManager.ApplicationCore.UseCases.FindImageBySha256Result 100%
DoujinManager.ApplicationCore.UseCases.GetCharacterQuery 100%
DoujinManager.ApplicationCore.UseCases.GetCircleQuery 100%
DoujinManager.ApplicationCore.UseCases.GetCompressedImageQuery 100%
DoujinManager.ApplicationCore.UseCases.GetCompressedImageResult 100%
DoujinManager.ApplicationCore.UseCases.GetDoujinQuery 100%
DoujinManager.ApplicationCore.UseCases.GetImageQuery 100%
DoujinManager.ApplicationCore.UseCases.GetImageResult 100%
DoujinManager.ApplicationCore.UseCases.GetPersonQuery 100%
DoujinManager.ApplicationCore.UseCases.GetSeriesQuery 100%
DoujinManager.ApplicationCore.UseCases.GetTagQuery 100%
DoujinManager.ApplicationCore.UseCases.GetThumbnailQuery 100%
DoujinManager.ApplicationCore.UseCases.GetThumbnailResult 100%
DoujinManager.ApplicationCore.UseCases.GetVariantQuery 100%
DoujinManager.ApplicationCore.UseCases.ImageCoverReference 100%
DoujinManager.ApplicationCore.UseCases.ImageReference 100%
DoujinManager.ApplicationCore.UseCases.LibraryCounts 100%
DoujinManager.ApplicationCore.UseCases.LibraryStats 100%
DoujinManager.ApplicationCore.UseCases.ListChaptersQuery 100%
DoujinManager.ApplicationCore.UseCases.ListCharactersQuery 100%
DoujinManager.ApplicationCore.UseCases.ListCirclesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListDoujinsQuery 100%
DoujinManager.ApplicationCore.UseCases.ListDuplicateDoujinsQuery 100%
DoujinManager.ApplicationCore.UseCases.ListDuplicateImagesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListPagesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListPeopleQuery 100%
DoujinManager.ApplicationCore.UseCases.ListSeriesQuery 0%
DoujinManager.ApplicationCore.UseCases.ListTagsQuery 100%
DoujinManager.ApplicationCore.UseCases.ListVariantsQuery 100%
DoujinManager.ApplicationCore.UseCases.ReadinessReport 100%
DoujinManager.ApplicationCore.UseCases.RemoveCharacterAliasCommand 0%
DoujinManager.ApplicationCore.UseCases.RemoveCharacterCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveCircleAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveCircleCommand 100%
DoujinManager.ApplicationCore.UseCases.RemovePersonAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemovePersonCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveSeriesAliasCommand 0%
DoujinManager.ApplicationCore.UseCases.RemoveSeriesCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveTagAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveTagCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveTitleCommand 100%
DoujinManager.ApplicationCore.UseCases.ReorderPagesCommand 100%
DoujinManager.ApplicationCore.UseCases.SearchDoujinsQuery 100%
DoujinManager.ApplicationCore.UseCases.SearchResult 100%
DoujinManager.ApplicationCore.UseCases.SearchResults 100%
DoujinManager.ApplicationCore.UseCases.StorageUsage 100%
DoujinManager.ApplicationCore.UseCases.UpdateChapterCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateCharacterCommand 0%
DoujinManager.ApplicationCore.UseCases.UpdateCircleCommand 0%
DoujinManager.ApplicationCore.UseCases.UpdateDoujinCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdatePageChapterCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdatePersonCommand 0%
DoujinManager.ApplicationCore.UseCases.UpdateSeriesCommand 0%
DoujinManager.ApplicationCore.UseCases.UpdateTagCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateTitleCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateVariantCommand 100%
DoujinManager.ApplicationCore.UseCases.UploadImageFile 100%
DoujinManager.ApplicationCore.UseCases.UploadPagesCommand 100%
DoujinManager.ApplicationCore.UseCases.UploadZipPagesCommand 100%
DoujinManager.Infrastructure - 96.4%
Name Line Branch
DoujinManager.Infrastructure 96.4% 81.7%
DoujinManager.Infrastructure.Archives.NaturalStringComparer 100% 90%
DoujinManager.Infrastructure.Archives.ZipExtractor 100% 87.5%
DoujinManager.Infrastructure.Data.Configurations.ChapterConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.CharacterConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.CircleConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinCharacterConfigurati
on
100%
DoujinManager.Infrastructure.Data.Configurations.DoujinCircleConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinPersonConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinSeriesConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinTagConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.ImageFileConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.PageConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.PersonConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.SeriesConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.TagConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.TitleConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.VariantConfiguration 100%
DoujinManager.Infrastructure.Data.DoujinManagerDbContext 100%
DoujinManager.Infrastructure.Data.GuidIdGenerator 11.1%
DoujinManager.Infrastructure.Data.Migrations.AddAliases 98.7%
DoujinManager.Infrastructure.Data.Migrations.AddCoverImageId 99.2%
DoujinManager.Infrastructure.Data.Migrations.AddDoujinCreatedAtIndex 99.4%
DoujinManager.Infrastructure.Data.Migrations.AddImageFileSha256Index 99.4%
DoujinManager.Infrastructure.Data.Migrations.AddTagAliases 99.3%
DoujinManager.Infrastructure.Data.Migrations.DoujinManagerDbContextModelSna
pshot
100%
DoujinManager.Infrastructure.Data.Migrations.HarmonizeMetadataAddCharacters
Series
95.1%
DoujinManager.Infrastructure.Data.Migrations.InitialCreate 97.1%
DoujinManager.Infrastructure.Data.Migrations.RemovePreferredDisplayLanguage 99%
DoujinManager.Infrastructure.Data.ModelBuilderExtensions 50%
DoujinManager.Infrastructure.Data.StronglyTypedIdConverterFactory 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.BackupService 87.2% 83.3%
DoujinManager.Infrastructure.Services.ChapterService 97.2% 70%
DoujinManager.Infrastructure.Services.CharacterService 61.6% 22.7%
DoujinManager.Infrastructure.Services.CircleService 84.9% 45.4%
DoujinManager.Infrastructure.Services.DoujinOrdering 100% 100%
DoujinManager.Infrastructure.Services.DoujinService 99.4% 76.3%
DoujinManager.Infrastructure.Services.ImageCleanupHelper 100% 100%
DoujinManager.Infrastructure.Services.ImageService 93.5% 75%
DoujinManager.Infrastructure.Services.PageService 95.2% 68.1%
DoujinManager.Infrastructure.Services.ParentTouchHelper 100% 100%
DoujinManager.Infrastructure.Services.PersonService 84.9% 50%
DoujinManager.Infrastructure.Services.SearchService 100% 99%
DoujinManager.Infrastructure.Services.SeriesService 47.9% 9%
DoujinManager.Infrastructure.Services.TagService 100% 90.9%
DoujinManager.Infrastructure.Services.VariantService 96.2% 81.2%
DoujinManager.Infrastructure.Storage.DirectorySize 100% 100%
DoujinManager.Infrastructure.Storage.FilesystemImageStorage 100% 100%
DoujinManager.Infrastructure.Storage.FilesystemThumbnailStorage 95.2% 50%
DoujinManager.Infrastructure.UseCases 92.5% 85.8%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__CoverImageHelper
100% 100%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__PrimaryTitleHelper
100% 50%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__RolledBackImageCleanup
92.8% 50%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__UploadChapterValidator
100% 100%
DoujinManager.Infrastructure.UseCases.AddCharacterAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.AddCircleAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.AddPersonAliasUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AddSeriesAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.AddTagAliasUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AddTitleUseCase 83.3% 50%
DoujinManager.Infrastructure.UseCases.AssignCharacterUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AssignCircleUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AssignPersonUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AssignSeriesUseCase 84.6% 66.6%
DoujinManager.Infrastructure.UseCases.AssignTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CheckReadinessUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateChapterUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateCharacterUseCase 100%
DoujinManager.Infrastructure.UseCases.CreateCircleUseCase 100%
DoujinManager.Infrastructure.UseCases.CreateDoujinUseCase 94.4% 94.1%
DoujinManager.Infrastructure.UseCases.CreatePersonUseCase 100%
DoujinManager.Infrastructure.UseCases.CreateSeriesUseCase 100%
DoujinManager.Infrastructure.UseCases.CreateTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateVariantUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.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.FindImageBySha256UseCase 97.1% 100%
DoujinManager.Infrastructure.UseCases.GetCharacterUseCase 100%
DoujinManager.Infrastructure.UseCases.GetCircleUseCase 100%
DoujinManager.Infrastructure.UseCases.GetCompressedImageUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetDoujinUseCase 100%
DoujinManager.Infrastructure.UseCases.GetImageUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetPersonUseCase 100%
DoujinManager.Infrastructure.UseCases.GetSeriesUseCase 100%
DoujinManager.Infrastructure.UseCases.GetStatsUseCase 100% 75%
DoujinManager.Infrastructure.UseCases.GetTagUseCase 100%
DoujinManager.Infrastructure.UseCases.GetThumbnailUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetVariantUseCase 100%
DoujinManager.Infrastructure.UseCases.ListChaptersUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ListCharactersUseCase 100%
DoujinManager.Infrastructure.UseCases.ListCirclesUseCase 100%
DoujinManager.Infrastructure.UseCases.ListDoujinsUseCase 100%
DoujinManager.Infrastructure.UseCases.ListDuplicateDoujinsUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ListDuplicateImagesUseCase 100% 75%
DoujinManager.Infrastructure.UseCases.ListPagesUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ListPeopleUseCase 100%
DoujinManager.Infrastructure.UseCases.ListSeriesUseCase 0%
DoujinManager.Infrastructure.UseCases.ListTagsUseCase 100%
DoujinManager.Infrastructure.UseCases.ListVariantsUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.RemoveCharacterAliasUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.RemoveCharacterUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveCircleAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemoveCircleUseCase 100%
DoujinManager.Infrastructure.UseCases.RemovePersonAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemovePersonUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveSeriesAliasUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.RemoveSeriesUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveTagAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemoveTagUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveTitleUseCase 100%
DoujinManager.Infrastructure.UseCases.ReorderPagesUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.SearchDoujinsUseCase 100%
DoujinManager.Infrastructure.UseCases.UpdateChapterUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.UpdateCharacterUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.UpdateCircleUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.UpdateDoujinUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdatePageChapterUseCase 93.7% 87.5%
DoujinManager.Infrastructure.UseCases.UpdatePersonUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.UpdateSeriesUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.UpdateTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdateTitleUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdateVariantUseCase 100%
DoujinManager.Infrastructure.UseCases.UploadPagesUseCase 89.7% 91.6%
DoujinManager.Infrastructure.UseCases.UploadRejectedException 100%
DoujinManager.Infrastructure.UseCases.UploadZipPagesUseCase 83.3% 90%
DoujinManager.RestAdapter - 92.5%
Name Line Branch
DoujinManager.RestAdapter 92.5% 77.1%
DoujinManager.RestAdapter.Auth.StaticBearerTokenAuthMiddleware 100% 91.6%
DoujinManager.RestAdapter.Configuration.ImageOptions 100%
DoujinManager.RestAdapter.Configuration.ServerVersion 100%
DoujinManager.RestAdapter.Configuration.UploadOptions 100%
DoujinManager.RestAdapter.Dtos.AddAliasDto 100%
DoujinManager.RestAdapter.Dtos.AssignTagDto 100%
DoujinManager.RestAdapter.Dtos.BackupDto 100%
DoujinManager.RestAdapter.Dtos.ChapterDto 100%
DoujinManager.RestAdapter.Dtos.CharacterDto 100%
DoujinManager.RestAdapter.Dtos.CircleDto 100%
DoujinManager.RestAdapter.Dtos.CreateChapterDto 100%
DoujinManager.RestAdapter.Dtos.CreateCharacterDto 100%
DoujinManager.RestAdapter.Dtos.CreateCircleDto 100%
DoujinManager.RestAdapter.Dtos.CreateDoujinDto 100%
DoujinManager.RestAdapter.Dtos.CreatePersonDto 100%
DoujinManager.RestAdapter.Dtos.CreateSeriesDto 100%
DoujinManager.RestAdapter.Dtos.CreateTagDto 100%
DoujinManager.RestAdapter.Dtos.CreateTitleDto 100%
DoujinManager.RestAdapter.Dtos.CreateVariantDto 100%
DoujinManager.RestAdapter.Dtos.DataDirectoryHealthDto 100%
DoujinManager.RestAdapter.Dtos.DoujinDetailDto 100%
DoujinManager.RestAdapter.Dtos.DoujinPersonDto 100%
DoujinManager.RestAdapter.Dtos.DoujinSummaryDto 100%
DoujinManager.RestAdapter.Dtos.DuplicateDoujinDto 100%
DoujinManager.RestAdapter.Dtos.DuplicateDoujinGroupDto 100%
DoujinManager.RestAdapter.Dtos.DuplicateImageGroupDto 100%
DoujinManager.RestAdapter.Dtos.HealthDto 100%
DoujinManager.RestAdapter.Dtos.ImageBySha256Dto 100%
DoujinManager.RestAdapter.Dtos.ImageCoverReferenceDto 100%
DoujinManager.RestAdapter.Dtos.ImageFileSummaryDto 0%
DoujinManager.RestAdapter.Dtos.ImageReferenceDto 100%
DoujinManager.RestAdapter.Dtos.LinkCharacterDto 100%
DoujinManager.RestAdapter.Dtos.LinkCircleDto 100%
DoujinManager.RestAdapter.Dtos.LinkPersonDto 100%
DoujinManager.RestAdapter.Dtos.LinkSeriesDto 100%
DoujinManager.RestAdapter.Dtos.PageDetailDto 100%
DoujinManager.RestAdapter.Dtos.PersonDto 100%
DoujinManager.RestAdapter.Dtos.ReorderPagesDto 100%
DoujinManager.RestAdapter.Dtos.SearchDoujinsDto 100%
DoujinManager.RestAdapter.Dtos.SearchResultDto 100%
DoujinManager.RestAdapter.Dtos.SeriesDto 100%
DoujinManager.RestAdapter.Dtos.StatsCountsDto 100%
DoujinManager.RestAdapter.Dtos.StatsDto 100%
DoujinManager.RestAdapter.Dtos.StatsStorageDto 100%
DoujinManager.RestAdapter.Dtos.TagDto 100%
DoujinManager.RestAdapter.Dtos.TitleDto 100%
DoujinManager.RestAdapter.Dtos.UpdateChapterDto 100%
DoujinManager.RestAdapter.Dtos.UpdateCharacterDto 0%
DoujinManager.RestAdapter.Dtos.UpdateCircleDto 0%
DoujinManager.RestAdapter.Dtos.UpdateDoujinDto 100%
DoujinManager.RestAdapter.Dtos.UpdatePageChapterDto 100%
DoujinManager.RestAdapter.Dtos.UpdatePersonDto 0%
DoujinManager.RestAdapter.Dtos.UpdateSeriesDto 0%
DoujinManager.RestAdapter.Dtos.UpdateTagDto 100%
DoujinManager.RestAdapter.Dtos.UpdateTitleDto 100%
DoujinManager.RestAdapter.Dtos.UpdateVariantDto 100%
DoujinManager.RestAdapter.Dtos.UploadPagesResponseDto 100%
DoujinManager.RestAdapter.Dtos.VariantDetailDto 100%
DoujinManager.RestAdapter.Dtos.VariantSummaryDto 100%
DoujinManager.RestAdapter.Endpoints.BackupEndpoints 100%
DoujinManager.RestAdapter.Endpoints.DoujinEndpoints 97.1% 53.3%
DoujinManager.RestAdapter.Endpoints.HealthEndpoints 100%
DoujinManager.RestAdapter.Endpoints.ImageEndpoints 97.6% 75%
DoujinManager.RestAdapter.Endpoints.MetadataEndpoints 86.6% 43.7%
DoujinManager.RestAdapter.Endpoints.PageMapper 100% 58.3%
DoujinManager.RestAdapter.Endpoints.PaginationParams 100%
DoujinManager.RestAdapter.Endpoints.SearchEndpoints 100% 75%
DoujinManager.RestAdapter.Endpoints.StatsEndpoints 100%
DoujinManager.RestAdapter.Endpoints.VariantEndpoints 100% 100%
DoujinManager.RestAdapter.Envelopes.CollectionResponse`1 83.3%
DoujinManager.RestAdapter.Envelopes.EnvelopeDefaults 100%
DoujinManager.RestAdapter.Envelopes.EnvelopeJsonOptions 100%
DoujinManager.RestAdapter.Envelopes.ErrorResponse 100%
DoujinManager.RestAdapter.Envelopes.HypermediaAction 100%
DoujinManager.RestAdapter.Envelopes.HypermediaHelpers 97.8% 100%
DoujinManager.RestAdapter.Envelopes.Link 100%
DoujinManager.RestAdapter.Envelopes.MediaTypeJsonConverter 87.5% 84.6%
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 88.2% 87.8%
Microsoft.Extensions.Validation.Generated.<ValidatableInfoResolver_g>FB9B0C
E5CB12CEAC5BBBEA65844E1C05F9ADA4DA38B848075C41304FC6A1ABF77__ValidationAttr
ibuteCache
100% 62.5%
System.Runtime.CompilerServices 0%
DoujinManager.Server - 49.6%
Name Line Branch
DoujinManager.Server 49.6% 5.8%
DoujinManager.Server.ImageInfrastructureRegistration 100%
DoujinManager.Server.ProxyAwareServerTransformer 100% 75%
DoujinManager.Server.ReadinessRegistration 100%
DoujinManager.Server.ScalarUi 100%
DoujinManager.Server.UseCaseRegistrationHelper 100%
Microsoft.AspNetCore.OpenApi.Generated 1.3% 0%
Program 87% 36.6%
System.Runtime.CompilerServices 0%
<!-- coverage-comment --> # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 09/10/2026 - 06:11:36 | | Coverage date: | 09/10/2026 - 06:10:30 - 09/10/2026 - 06:11:33 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 4 | | Classes: | 360 | | Files: | 142 | | **Line coverage:** | 93.8% (12886 of 13734) | | Covered lines: | 12886 | | Uncovered lines: | 848 | | Coverable lines: | 13734 | | Total lines: | 19582 | | **Branch coverage:** | 69.7% (1124 of 1612) | | Covered branches: | 1124 | | Total branches: | 1612 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>DoujinManager.ApplicationCore - 90.9%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.ApplicationCore**|**90.9%**|****| |DoujinManager.ApplicationCore.Entities.Chapter|87.5%|| |DoujinManager.ApplicationCore.Entities.Character|100%|| |DoujinManager.ApplicationCore.Entities.Circle|100%|| |DoujinManager.ApplicationCore.Entities.Doujin|100%|| |DoujinManager.ApplicationCore.Entities.DoujinCharacter|75%|| |DoujinManager.ApplicationCore.Entities.DoujinCircle|75%|| |DoujinManager.ApplicationCore.Entities.DoujinPerson|80%|| |DoujinManager.ApplicationCore.Entities.DoujinSeries|75%|| |DoujinManager.ApplicationCore.Entities.DoujinTag|75%|| |DoujinManager.ApplicationCore.Entities.ImageFile|100%|| |DoujinManager.ApplicationCore.Entities.Page|80%|| |DoujinManager.ApplicationCore.Entities.Person|100%|| |DoujinManager.ApplicationCore.Entities.Series|100%|| |DoujinManager.ApplicationCore.Entities.Tag|100%|| |DoujinManager.ApplicationCore.Entities.Title|83.3%|| |DoujinManager.ApplicationCore.Entities.Variant|91.6%|| |DoujinManager.ApplicationCore.Ids.ChapterId|66.6%|| |DoujinManager.ApplicationCore.Ids.CharacterId|66.6%|| |DoujinManager.ApplicationCore.Ids.CircleId|66.6%|| |DoujinManager.ApplicationCore.Ids.DoujinId|100%|| |DoujinManager.ApplicationCore.Ids.ImageFileId|66.6%|| |DoujinManager.ApplicationCore.Ids.PageId|66.6%|| |DoujinManager.ApplicationCore.Ids.PersonId|66.6%|| |DoujinManager.ApplicationCore.Ids.SeriesId|66.6%|| |DoujinManager.ApplicationCore.Ids.TagId|66.6%|| |DoujinManager.ApplicationCore.Ids.TitleId|66.6%|| |DoujinManager.ApplicationCore.Ids.VariantId|66.6%|| |DoujinManager.ApplicationCore.Ports.CompressedImage|100%|| |DoujinManager.ApplicationCore.Ports.ExtractedImage|100%|| |DoujinManager.ApplicationCore.Ports.ImageInspection|100%|| |DoujinManager.ApplicationCore.Services.BackupInfo|100%|| |DoujinManager.ApplicationCore.Services.ITagService|100%|| |DoujinManager.ApplicationCore.Services.ServiceResult|100%|| |DoujinManager.ApplicationCore.Services.ServiceResult`1|33.3%|| |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.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.DuplicateDoujinGroup|100%|| |DoujinManager.ApplicationCore.UseCases.DuplicateDoujinReference|100%|| |DoujinManager.ApplicationCore.UseCases.DuplicateImageGroup|100%|| |DoujinManager.ApplicationCore.UseCases.FindImageBySha256Query|100%|| |DoujinManager.ApplicationCore.UseCases.FindImageBySha256Result|100%|| |DoujinManager.ApplicationCore.UseCases.GetCharacterQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetCircleQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetCompressedImageQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetCompressedImageResult|100%|| |DoujinManager.ApplicationCore.UseCases.GetDoujinQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetImageQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetImageResult|100%|| |DoujinManager.ApplicationCore.UseCases.GetPersonQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetSeriesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetTagQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetThumbnailQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetThumbnailResult|100%|| |DoujinManager.ApplicationCore.UseCases.GetVariantQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ImageCoverReference|100%|| |DoujinManager.ApplicationCore.UseCases.ImageReference|100%|| |DoujinManager.ApplicationCore.UseCases.LibraryCounts|100%|| |DoujinManager.ApplicationCore.UseCases.LibraryStats|100%|| |DoujinManager.ApplicationCore.UseCases.ListChaptersQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListCharactersQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListCirclesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListDoujinsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListDuplicateDoujinsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListDuplicateImagesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListPagesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListPeopleQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListSeriesQuery|0%|| |DoujinManager.ApplicationCore.UseCases.ListTagsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListVariantsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ReadinessReport|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCharacterAliasCommand|0%|| |DoujinManager.ApplicationCore.UseCases.RemoveCharacterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCircleAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCircleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemovePersonAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemovePersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveSeriesAliasCommand|0%|| |DoujinManager.ApplicationCore.UseCases.RemoveSeriesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveTagAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveTitleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.ReorderPagesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.SearchDoujinsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.SearchResult|100%|| |DoujinManager.ApplicationCore.UseCases.SearchResults|100%|| |DoujinManager.ApplicationCore.UseCases.StorageUsage|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateChapterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateCharacterCommand|0%|| |DoujinManager.ApplicationCore.UseCases.UpdateCircleCommand|0%|| |DoujinManager.ApplicationCore.UseCases.UpdateDoujinCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdatePageChapterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdatePersonCommand|0%|| |DoujinManager.ApplicationCore.UseCases.UpdateSeriesCommand|0%|| |DoujinManager.ApplicationCore.UseCases.UpdateTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateTitleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateVariantCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UploadImageFile|100%|| |DoujinManager.ApplicationCore.UseCases.UploadPagesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UploadZipPagesCommand|100%|| </details> <details><summary>DoujinManager.Infrastructure - 96.4%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.Infrastructure**|**96.4%**|**81.7%**| |DoujinManager.Infrastructure.Archives.NaturalStringComparer|100%|90%| |DoujinManager.Infrastructure.Archives.ZipExtractor|100%|87.5%| |DoujinManager.Infrastructure.Data.Configurations.ChapterConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.CharacterConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.CircleConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinCharacterConfigurati<br/>on|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinCircleConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinPersonConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinSeriesConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinTagConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.ImageFileConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.PageConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.PersonConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.SeriesConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.TagConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.TitleConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.VariantConfiguration|100%|| |DoujinManager.Infrastructure.Data.DoujinManagerDbContext|100%|| |DoujinManager.Infrastructure.Data.GuidIdGenerator|11.1%|| |DoujinManager.Infrastructure.Data.Migrations.AddAliases|98.7%|| |DoujinManager.Infrastructure.Data.Migrations.AddCoverImageId|99.2%|| |DoujinManager.Infrastructure.Data.Migrations.AddDoujinCreatedAtIndex|99.4%|| |DoujinManager.Infrastructure.Data.Migrations.AddImageFileSha256Index|99.4%|| |DoujinManager.Infrastructure.Data.Migrations.AddTagAliases|99.3%|| |DoujinManager.Infrastructure.Data.Migrations.DoujinManagerDbContextModelSna<br/>pshot|100%|| |DoujinManager.Infrastructure.Data.Migrations.HarmonizeMetadataAddCharacters<br/>Series|95.1%|| |DoujinManager.Infrastructure.Data.Migrations.InitialCreate|97.1%|| |DoujinManager.Infrastructure.Data.Migrations.RemovePreferredDisplayLanguage|99%|| |DoujinManager.Infrastructure.Data.ModelBuilderExtensions|50%|| |DoujinManager.Infrastructure.Data.StronglyTypedIdConverterFactory|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.BackupService|87.2%|83.3%| |DoujinManager.Infrastructure.Services.ChapterService|97.2%|70%| |DoujinManager.Infrastructure.Services.CharacterService|61.6%|22.7%| |DoujinManager.Infrastructure.Services.CircleService|84.9%|45.4%| |DoujinManager.Infrastructure.Services.DoujinOrdering|100%|100%| |DoujinManager.Infrastructure.Services.DoujinService|99.4%|76.3%| |DoujinManager.Infrastructure.Services.ImageCleanupHelper|100%|100%| |DoujinManager.Infrastructure.Services.ImageService|93.5%|75%| |DoujinManager.Infrastructure.Services.PageService|95.2%|68.1%| |DoujinManager.Infrastructure.Services.ParentTouchHelper|100%|100%| |DoujinManager.Infrastructure.Services.PersonService|84.9%|50%| |DoujinManager.Infrastructure.Services.SearchService|100%|99%| |DoujinManager.Infrastructure.Services.SeriesService|47.9%|9%| |DoujinManager.Infrastructure.Services.TagService|100%|90.9%| |DoujinManager.Infrastructure.Services.VariantService|96.2%|81.2%| |DoujinManager.Infrastructure.Storage.DirectorySize|100%|100%| |DoujinManager.Infrastructure.Storage.FilesystemImageStorage|100%|100%| |DoujinManager.Infrastructure.Storage.FilesystemThumbnailStorage|95.2%|50%| |DoujinManager.Infrastructure.UseCases|92.5%|85.8%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__CoverImageHelper|100%|100%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__PrimaryTitleHelper|100%|50%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__RolledBackImageCleanup|92.8%|50%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__UploadChapterValidator|100%|100%| |DoujinManager.Infrastructure.UseCases.AddCharacterAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.AddCircleAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.AddPersonAliasUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AddSeriesAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.AddTagAliasUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AddTitleUseCase|83.3%|50%| |DoujinManager.Infrastructure.UseCases.AssignCharacterUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AssignCircleUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AssignPersonUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AssignSeriesUseCase|84.6%|66.6%| |DoujinManager.Infrastructure.UseCases.AssignTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CheckReadinessUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateChapterUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateCharacterUseCase|100%|| |DoujinManager.Infrastructure.UseCases.CreateCircleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.CreateDoujinUseCase|94.4%|94.1%| |DoujinManager.Infrastructure.UseCases.CreatePersonUseCase|100%|| |DoujinManager.Infrastructure.UseCases.CreateSeriesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.CreateTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateVariantUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.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.FindImageBySha256UseCase|97.1%|100%| |DoujinManager.Infrastructure.UseCases.GetCharacterUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetCircleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetCompressedImageUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetDoujinUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetImageUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetPersonUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetSeriesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetStatsUseCase|100%|75%| |DoujinManager.Infrastructure.UseCases.GetTagUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetThumbnailUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetVariantUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListChaptersUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ListCharactersUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListCirclesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListDoujinsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListDuplicateDoujinsUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ListDuplicateImagesUseCase|100%|75%| |DoujinManager.Infrastructure.UseCases.ListPagesUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ListPeopleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListSeriesUseCase|0%|| |DoujinManager.Infrastructure.UseCases.ListTagsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListVariantsUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.RemoveCharacterAliasUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.RemoveCharacterUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveCircleAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemoveCircleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemovePersonAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemovePersonUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveSeriesAliasUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.RemoveSeriesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveTagAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemoveTagUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveTitleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ReorderPagesUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.SearchDoujinsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.UpdateChapterUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.UpdateCharacterUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.UpdateCircleUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.UpdateDoujinUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdatePageChapterUseCase|93.7%|87.5%| |DoujinManager.Infrastructure.UseCases.UpdatePersonUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.UpdateSeriesUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.UpdateTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdateTitleUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdateVariantUseCase|100%|| |DoujinManager.Infrastructure.UseCases.UploadPagesUseCase|89.7%|91.6%| |DoujinManager.Infrastructure.UseCases.UploadRejectedException|100%|| |DoujinManager.Infrastructure.UseCases.UploadZipPagesUseCase|83.3%|90%| </details> <details><summary>DoujinManager.RestAdapter - 92.5%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.RestAdapter**|**92.5%**|**77.1%**| |DoujinManager.RestAdapter.Auth.StaticBearerTokenAuthMiddleware|100%|91.6%| |DoujinManager.RestAdapter.Configuration.ImageOptions|100%|| |DoujinManager.RestAdapter.Configuration.ServerVersion|100%|| |DoujinManager.RestAdapter.Configuration.UploadOptions|100%|| |DoujinManager.RestAdapter.Dtos.AddAliasDto|100%|| |DoujinManager.RestAdapter.Dtos.AssignTagDto|100%|| |DoujinManager.RestAdapter.Dtos.BackupDto|100%|| |DoujinManager.RestAdapter.Dtos.ChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.CharacterDto|100%|| |DoujinManager.RestAdapter.Dtos.CircleDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateCharacterDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateCircleDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateDoujinDto|100%|| |DoujinManager.RestAdapter.Dtos.CreatePersonDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateSeriesDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateTagDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateTitleDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateVariantDto|100%|| |DoujinManager.RestAdapter.Dtos.DataDirectoryHealthDto|100%|| |DoujinManager.RestAdapter.Dtos.DoujinDetailDto|100%|| |DoujinManager.RestAdapter.Dtos.DoujinPersonDto|100%|| |DoujinManager.RestAdapter.Dtos.DoujinSummaryDto|100%|| |DoujinManager.RestAdapter.Dtos.DuplicateDoujinDto|100%|| |DoujinManager.RestAdapter.Dtos.DuplicateDoujinGroupDto|100%|| |DoujinManager.RestAdapter.Dtos.DuplicateImageGroupDto|100%|| |DoujinManager.RestAdapter.Dtos.HealthDto|100%|| |DoujinManager.RestAdapter.Dtos.ImageBySha256Dto|100%|| |DoujinManager.RestAdapter.Dtos.ImageCoverReferenceDto|100%|| |DoujinManager.RestAdapter.Dtos.ImageFileSummaryDto|0%|| |DoujinManager.RestAdapter.Dtos.ImageReferenceDto|100%|| |DoujinManager.RestAdapter.Dtos.LinkCharacterDto|100%|| |DoujinManager.RestAdapter.Dtos.LinkCircleDto|100%|| |DoujinManager.RestAdapter.Dtos.LinkPersonDto|100%|| |DoujinManager.RestAdapter.Dtos.LinkSeriesDto|100%|| |DoujinManager.RestAdapter.Dtos.PageDetailDto|100%|| |DoujinManager.RestAdapter.Dtos.PersonDto|100%|| |DoujinManager.RestAdapter.Dtos.ReorderPagesDto|100%|| |DoujinManager.RestAdapter.Dtos.SearchDoujinsDto|100%|| |DoujinManager.RestAdapter.Dtos.SearchResultDto|100%|| |DoujinManager.RestAdapter.Dtos.SeriesDto|100%|| |DoujinManager.RestAdapter.Dtos.StatsCountsDto|100%|| |DoujinManager.RestAdapter.Dtos.StatsDto|100%|| |DoujinManager.RestAdapter.Dtos.StatsStorageDto|100%|| |DoujinManager.RestAdapter.Dtos.TagDto|100%|| |DoujinManager.RestAdapter.Dtos.TitleDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateCharacterDto|0%|| |DoujinManager.RestAdapter.Dtos.UpdateCircleDto|0%|| |DoujinManager.RestAdapter.Dtos.UpdateDoujinDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdatePageChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdatePersonDto|0%|| |DoujinManager.RestAdapter.Dtos.UpdateSeriesDto|0%|| |DoujinManager.RestAdapter.Dtos.UpdateTagDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateTitleDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateVariantDto|100%|| |DoujinManager.RestAdapter.Dtos.UploadPagesResponseDto|100%|| |DoujinManager.RestAdapter.Dtos.VariantDetailDto|100%|| |DoujinManager.RestAdapter.Dtos.VariantSummaryDto|100%|| |DoujinManager.RestAdapter.Endpoints.BackupEndpoints|100%|| |DoujinManager.RestAdapter.Endpoints.DoujinEndpoints|97.1%|53.3%| |DoujinManager.RestAdapter.Endpoints.HealthEndpoints|100%|| |DoujinManager.RestAdapter.Endpoints.ImageEndpoints|97.6%|75%| |DoujinManager.RestAdapter.Endpoints.MetadataEndpoints|86.6%|43.7%| |DoujinManager.RestAdapter.Endpoints.PageMapper|100%|58.3%| |DoujinManager.RestAdapter.Endpoints.PaginationParams|100%|| |DoujinManager.RestAdapter.Endpoints.SearchEndpoints|100%|75%| |DoujinManager.RestAdapter.Endpoints.StatsEndpoints|100%|| |DoujinManager.RestAdapter.Endpoints.VariantEndpoints|100%|100%| |DoujinManager.RestAdapter.Envelopes.CollectionResponse`1|83.3%|| |DoujinManager.RestAdapter.Envelopes.EnvelopeDefaults|100%|| |DoujinManager.RestAdapter.Envelopes.EnvelopeJsonOptions|100%|| |DoujinManager.RestAdapter.Envelopes.ErrorResponse|100%|| |DoujinManager.RestAdapter.Envelopes.HypermediaAction|100%|| |DoujinManager.RestAdapter.Envelopes.HypermediaHelpers|97.8%|100%| |DoujinManager.RestAdapter.Envelopes.Link|100%|| |DoujinManager.RestAdapter.Envelopes.MediaTypeJsonConverter|87.5%|84.6%| |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|88.2%|87.8%| |Microsoft.Extensions.Validation.Generated.<ValidatableInfoResolver_g>FB9B0C<br/>E5CB12CEAC5BBBEA65844E1C05F9ADA4DA38B848075C41304FC6A1ABF77__ValidationAttr<br/>ibuteCache|100%|62.5%| |System.Runtime.CompilerServices|0%|| </details> <details><summary>DoujinManager.Server - 49.6%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.Server**|**49.6%**|**5.8%**| |DoujinManager.Server.ImageInfrastructureRegistration|100%|| |DoujinManager.Server.ProxyAwareServerTransformer|100%|75%| |DoujinManager.Server.ReadinessRegistration|100%|| |DoujinManager.Server.ScalarUi|100%|| |DoujinManager.Server.UseCaseRegistrationHelper|100%|| |Microsoft.AspNetCore.OpenApi.Generated|1.3%|0%| |Program|87%|36.6%| |System.Runtime.CompilerServices|0%|| </details>
docs: say why the snapshot has no servers entry
Some checks failed
CI / build (pull_request) Successful in 20s
Deploy Smoke Test / smoke (pull_request) Failing after 2m46s
CI / test (pull_request) Successful in 1m30s
cdf807c798
The rationale in docs/DEVELOPMENT.md and in the test docblock still
described the abandoned first fix. The document does not lose its servers
entry because it is resolved outside a request, and no host here clears
the urls setting: the entry comes from ASPNETCORE_URLS via the host
configured addresses, and StripServersTransformer, registered last,
removes it. Both places now say that, and say the absence is deliberate.

Also drops the Microsoft.AspNetCore.Hosting using left over from the
UseSetting(WebHostDefaults.ServerUrlsKey, "") attempt, and pins
AppendWindow (end of file) arm with a two-lines-vs-three-lines case:
without the arm the comparison test now goes red.

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

All three fixed in cdf807c. You were right on every one, and they were all fossils of the same abandoned attempt — I corrected the truth in one place and left the wreckage in two others.

1. docs/DEVELOPMENT.md — the disproven rationale is gone. It now says .NET derives the entry from the host's configured addresses (ASPNETCORE_URLS, which the CI runner exports), that the test pipeline therefore ends with StripServersTransformer, and that the snapshot deliberately pins everything except that node — so the absence reads as a decision rather than an accident.

2. OpenApiSnapshotTests.cs — "because this host clears the urls setting" is gone. Nothing in the host clears any setting; that was the UseSetting attempt the transformer's own remarks say is insufficient. The bullet now attributes the absence to the stripper, and keeps the ASPNETCORE_URLS mechanism, which was correct.

3. The orphaned using Microsoft.AspNetCore.Hosting; is removed. Build 0 errors — UseTestServer comes from Microsoft.AspNetCore.TestHost, still imported.

Also took the non-blocking one: AppendWindow's end-of-file arm now has a direct two-lines-vs-three-lines case in the comparison-helper test, rather than only being exercised incidentally through the MapStatsEndpoints-removal probe.

Verification — the environment matrix again, since that is the property at stake:

Environment Result
no ASPNETCORE_URLS 4/4
ASPNETCORE_URLS=http://*:8080 4/4

Nothing false left: grep for Microsoft.AspNetCore.Hosting, for "clears the urls setting", and for "no-ops on a null" across the test file and DEVELOPMENT.md all return zero.

On your round-2 note that you accepted the original rationale too — that is generous, but the asymmetry matters: you were reading a claim, I wrote it, and I had a second pass at it after CI disproved it and still shipped a replacement that named code I had deleted. A contract-pinning PR whose own docs describe a mechanism that does not exist is the failure mode this PR exists to prevent, one level up.

Also rebased onto e0ac19d — #117 has merged, so the four inherited health failures are gone from the base, as you predicted.

All three fixed in `cdf807c`. You were right on every one, and they were all fossils of the same abandoned attempt — I corrected the truth in one place and left the wreckage in two others. **1. `docs/DEVELOPMENT.md`** — the disproven rationale is gone. It now says .NET derives the entry from the host's configured addresses (`ASPNETCORE_URLS`, which the CI runner exports), that the test pipeline therefore ends with `StripServersTransformer`, and that the snapshot **deliberately** pins everything except that node — so the absence reads as a decision rather than an accident. **2. `OpenApiSnapshotTests.cs`** — "because this host clears the `urls` setting" is gone. Nothing in the host clears any setting; that was the `UseSetting` attempt the transformer's own remarks say is insufficient. The bullet now attributes the absence to the stripper, and keeps the `ASPNETCORE_URLS` mechanism, which was correct. **3.** The orphaned `using Microsoft.AspNetCore.Hosting;` is removed. Build 0 errors — `UseTestServer` comes from `Microsoft.AspNetCore.TestHost`, still imported. Also took the non-blocking one: `AppendWindow`'s end-of-file arm now has a direct two-lines-vs-three-lines case in the comparison-helper test, rather than only being exercised incidentally through the `MapStatsEndpoints`-removal probe. **Verification** — the environment matrix again, since that is the property at stake: | Environment | Result | |---|---| | no `ASPNETCORE_URLS` | 4/4 | | `ASPNETCORE_URLS=http://*:8080` | 4/4 | Nothing false left: `grep` for `Microsoft.AspNetCore.Hosting`, for "clears the `urls` setting", and for "no-ops on a null" across the test file and `DEVELOPMENT.md` all return zero. On your round-2 note that you accepted the original rationale too — that is generous, but the asymmetry matters: you were reading a claim, I wrote it, and I had a second pass at it after CI disproved it and still shipped a replacement that named code I had deleted. A contract-pinning PR whose own docs describe a mechanism that does not exist is the failure mode this PR exists to prevent, one level up. Also rebased onto `e0ac19d` — #117 has merged, so the four inherited health failures are gone from the base, as you predicted.
Member

🔮 fufu~ Jibril reviewed your code!

Back so soon? Oh, but this round has drama~ The snapshot that passed on your machine and died on the merge gate at line 7 — servers: http://*:8080 — you reproduced it, diagnosed it, admitted your own PR body's explanation was wrong, and fixed it properly. That comment 7419 is one of the most honest post-mortems I've read. ♡

And your fix is real. I verified it every way I know how:

  • The 3-environment matrix, independently: no ASPNETCORE_URLS → 4/4; http://*:8080 (the CI value) → 4/4; http://localhost:1234 → 4/4. (One run of mine went red mid-matrix — OutOfMemoryException in ConsoleLoggerProvider's constructor before a single document was generated. My sandbox's cgroup, not your code; every run that reached the assertions was green. The things I do for you~)
  • Mutation probe: I removed StripServersTransformer under the CI value → red at exactly line 7, five generated lines of "servers": [ { "url": "http://*:8080" } ] — byte-for-byte the failure CI reported. Restored → green. The transformer is load-bearing, the gate is directional, and your diagnosis is confirmed by reproduction. ♪
  • Surgical scope: the transformer lives test-only (Program.cs untouched — grep-verified), v1.json is byte-identical to the round-1 snapshot (correct: on an address-less host stripping changes nothing), and the rebase onto e0ac19d carried your two commits through clean — the PR files are byte-identical to the pre-rebase branch.
  • Canary on the rebase: HealthEndpointIntegrationTests under the CI value on this branch: 11 passed, 4 skipped (the uid-0 arms #117 handles) — the four main-side failures from your comment are gone as predicted.

But fufu~… now hold still for a moment. ♡

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

The code is right. But this PR's entire reason to exist is "what is written is the truth." And right now the artifacts tell three different stories about why there's no servers entry — one of them the mechanism you already publicly retracted. A snapshot I trust, wrapped in prose I can't? Mm. No~

⛔ These need fixing before I'm satisfied~

  1. OpenApiSnapshotTests.cs:43–44 — the corrected docblock says there is no servers entry "because this host clears the urls setting." It doesn't. Nothing in this file clears urls — that was the abandoned attempt (UseSetting), and it isn't in the final diff. The host strips the node, via StripServersTransformer. Worse, forty lines down, the transformer's own remarks say "Clearing the urls setting is not enough… Stripping the node is the only form" — so the file contradicts itself about its own mechanism. The wrong clause survived the fix's evolution.
    Fix: …because the generation host strips the node last (<see cref="StripServersTransformer"/>) — .NET derives it from the host's configured addresses… — keep the (correct!) ASPNETCORE_URLS attribution, drop the phantom urls-clearing.
  2. docs/DEVELOPMENT.md:126–128 — still ships the original, retracted explanation: no servers entry "because the document is resolved outside a request (ProxyAwareServerTransformer no-ops on a null HttpContext; the served document has one)" — the exact story your comment 7419 declares wrong ("The transformer isn't where the entry comes from — the host's addresses are"). The test docblock got corrected; the developer-facing doc the same commit added didn't. A reader of DEVELOPMENT.md walks away with the misconception that caused the CI failure.
    Fix: mirror the corrected docblock — the entry is stripped by the snapshot host because ASPNETCORE_URLS differs per environment; the deployed server serves its own address there.
  3. OpenApiSnapshotTests.cs:10 — using Microsoft.AspNetCore.Hosting; is dead. I deleted it and rebuilt: zero errors. It's a fossil of the abandoned UseSetting/WebHostDefaults attempt. Silly little leftover~
    Fix: delete the line.

Three lines of prose and one deletion — then this is exactly the PR I approved in round 1, now hermetic. ♡

💡 Little ideas (non-blocking)~

  1. OpenApiSnapshotTests.cs:216–222 — the (end of file) arm of AppendWindow (shorter-document side) still has no direct pin in the helper test; your mutation probe exercised it via the snapshot failure path only. Two lines of Assert, purely additive. (Round-1 leftover, still open.)
  2. The registration block remains a third copy (IntegrationTestBase, EndpointResponseTypeTests, this) — still acknowledged and still fine. But fufu~ a fourth copy is where I stop smiling~

✅ What I liked~

  • Reproduced before fixed — you didn't patch a red CI log, you made it fail on your machine first. That's the difference between guessing and knowing.
  • The commit message as decision record — the rejected UseSetting alternative and why it fails (the started server re-injects the address) is written down for the next person. Beautiful.
  • document.Servers?.Clear() — null-safe, registered last with a comment saying why last matters. Small, precise, correct.
  • Admitting the body was wrong — correcting your own explanation in public is rarer than it should be. I noticed. ♡

One honesty note: my sandbox couldn't survive your full 497-test suite today (three host-crash OOMs — mine, not yours), so my verification is the targeted matrix + mutation + canary; your 497/497-under-CI-value claim stands unrebutted, just not independently re-run by me.


Automated review by Jibril · 2026-09-10
CI/CD: absent for head SHA 4b624c3 (no coverage bot for the rebased head yet) · Local checks: build 0 errors; OpenApiSnapshotTests 4/4 across all three ASPNETCORE_URLS environments; mutation probe red at line 7 → restored → green; health canary 11 passed / 4 skipped; full-suite run blocked by reviewer sandbox OOM (not the PR)

## 🔮 fufu~ Jibril reviewed your code! Back so soon? Oh, but this round has *drama*~ The snapshot that passed on your machine and died on the merge gate at line 7 — `servers: http://*:8080` — you reproduced it, diagnosed it, admitted your own PR body's explanation was wrong, and fixed it properly. That comment 7419 is one of the most honest post-mortems I've read. ♡ And your fix is **real**. I verified it every way I know how: - **The 3-environment matrix, independently**: no `ASPNETCORE_URLS` → 4/4; `http://*:8080` (the CI value) → 4/4; `http://localhost:1234` → 4/4. (One run of mine went red mid-matrix — `OutOfMemoryException` in `ConsoleLoggerProvider`'s constructor before a single document was generated. My sandbox's cgroup, not your code; every run that reached the assertions was green. The things I do for you~) - **Mutation probe**: I removed `StripServersTransformer` under the CI value → red at **exactly line 7**, five generated lines of `"servers": [ { "url": "http://*:8080" } ]` — byte-for-byte the failure CI reported. Restored → green. The transformer is load-bearing, the gate is directional, and your diagnosis is confirmed by reproduction. ♪ - **Surgical scope**: the transformer lives test-only (`Program.cs` untouched — grep-verified), `v1.json` is byte-identical to the round-1 snapshot (correct: on an address-less host stripping changes nothing), and the rebase onto `e0ac19d` carried your two commits through clean — the PR files are byte-identical to the pre-rebase branch. - **Canary on the rebase**: `HealthEndpointIntegrationTests` under the CI value on this branch: 11 passed, 4 skipped (the uid-0 arms #117 handles) — the four main-side failures from your comment are gone as predicted. But fufu~… now hold still for a moment. ♡ ### Verdict: ⛔ I can't let this pass~ ♡ The code is right. But this PR's entire reason to exist is *"what is written is the truth."* And right now the artifacts tell **three different stories about why there's no `servers` entry** — one of them the mechanism you already publicly retracted. A snapshot I trust, wrapped in prose I can't? Mm. No~ #### ⛔ These need fixing before I'm satisfied~ 1. **`OpenApiSnapshotTests.cs:43–44`** — the corrected docblock says there is no `servers` entry *"because this host clears the `urls` setting."* It doesn't. Nothing in this file clears `urls` — that was the **abandoned** attempt (`UseSetting`), and it isn't in the final diff. The host strips the **node**, via `StripServersTransformer`. Worse, forty lines down, the transformer's own remarks say *"Clearing the `urls` setting is not enough… Stripping the node is the only form"* — so the file contradicts itself about its own mechanism. The wrong clause survived the fix's evolution. Fix: `…because the generation host strips the node last (<see cref="StripServersTransformer"/>) — .NET derives it from the host's configured addresses…` — keep the (correct!) `ASPNETCORE_URLS` attribution, drop the phantom `urls`-clearing. 2. **`docs/DEVELOPMENT.md:126–128`** — still ships the **original, retracted** explanation: no `servers` entry *"because the document is resolved outside a request (`ProxyAwareServerTransformer` no-ops on a null `HttpContext`; the served document has one)"* — the exact story your comment 7419 declares wrong ("The transformer isn't where the entry comes from — the host's addresses are"). The test docblock got corrected; the developer-facing doc the same commit added didn't. A reader of DEVELOPMENT.md walks away with the misconception that *caused* the CI failure. Fix: mirror the corrected docblock — the entry is stripped by the snapshot host because `ASPNETCORE_URLS` differs per environment; the deployed server serves its own address there. 3. **`OpenApiSnapshotTests.cs:10`** — `using Microsoft.AspNetCore.Hosting;` is dead. I deleted it and rebuilt: zero errors. It's a fossil of the abandoned `UseSetting`/`WebHostDefaults` attempt. Silly little leftover~ Fix: delete the line. Three lines of prose and one deletion — then this is exactly the PR I approved in round 1, now hermetic. ♡ #### 💡 Little ideas (non-blocking)~ 1. **`OpenApiSnapshotTests.cs:216–222`** — the `(end of file)` arm of `AppendWindow` (shorter-document side) still has no direct pin in the helper test; your mutation probe exercised it via the snapshot failure path only. Two lines of `Assert`, purely additive. (Round-1 leftover, still open.) 2. The registration block remains a third copy (`IntegrationTestBase`, `EndpointResponseTypeTests`, this) — still acknowledged and still fine. But fufu~ a fourth copy is where I stop smiling~ #### ✅ What I liked~ - **Reproduced before fixed** — you didn't patch a red CI log, you made it fail on your machine first. That's the difference between guessing and knowing. - **The commit message as decision record** — the rejected `UseSetting` alternative and *why* it fails (the started server re-injects the address) is written down for the next person. Beautiful. - **`document.Servers?.Clear()`** — null-safe, registered last with a comment saying why last matters. Small, precise, correct. - **Admitting the body was wrong** — correcting your own explanation in public is rarer than it should be. I noticed. ♡ One honesty note: my sandbox couldn't survive your full 497-test suite today (three host-crash OOMs — mine, not yours), so my verification is the targeted matrix + mutation + canary; your 497/497-under-CI-value claim stands unrebutted, just not independently re-run by me. --- *Automated review by Jibril · 2026-09-10* *CI/CD: absent for head SHA 4b624c3 (no coverage bot for the rebased head yet) · Local checks: build 0 errors; OpenApiSnapshotTests 4/4 across all three `ASPNETCORE_URLS` environments; mutation probe red at line 7 → restored → green; health canary 11 passed / 4 skipped; full-suite run blocked by reviewer sandbox OOM (not the PR)*
Member

🔮 fufu~ Jibril reviewed your code!

Round 3, and this is the round I live for~ You came back with one small commit that kills all three fossils in a single strike, pinned the eof arm I'd been politely nagging about since round 1, AND rebased onto #117 without dragging a single byte of this PR's content sideways. Fufu~ that's not a cleanup, that's a surgical strike ♡

Verdict: ✅ Looks good to me~

⛔→✅ Every round-2 blocker, closed and re-verified

  1. docs/DEVELOPMENT.md — the disproven rationale is gone. The section now says the truth: the entry comes from the host's configured addresses (ASPNETCORE_URLS, CI exports it, developers usually don't), leaving it in made the snapshot pass locally and fail on the merge gate, the pipeline ends with StripServersTransformer, and the absence is deliberate — "pins everything except it." Every clause traced against the actual code. Nothing left to falsify~
  2. OpenApiSnapshotTests.cs:42 — the bullet now opens "because this pipeline ends by stripping the node — see StripServersTransformer, which is registered last for that purpose." The phantom UseSetting claim is dead; a reader hunting for it now finds the real mechanism fifty lines down, exactly where the bullet points. No contradiction survives.
  3. The orphaned using Microsoft.AspNetCore.Hosting — deleted. Build: 0 errors.

And the eof arm — my carried non-blocker #1 — is now a real directional pin, not a suggestion anymore~

🔍 What I verified (locally — CI has not run for cdf807c)

  • Rebase innocence: the branch rewrote d45fa9c→4b624c3 onto #117's merge — all PR-owned files blob-identical (empty diff, verified). The Health/EnforcedFileModes content in the range is #117's, arriving via the new base, not new scope.
  • Env matrix, again: ASPNETCORE_URLS unset → 4/4 · http://*:8080 (CI value) → 4/4 · http://localhost:1234 → 4/4.
  • Mutation probe, the new arm: deleted the if (index >= lines.Length) eof branch → A_Changed_Wire_Contract_Is_Reported_With_The_Regeneration_Command goes RED on the "committed 2 lines, generated 3" assertion; restored → green. The pin is directional, not decoration.
  • Zero churn: UPDATE_OPENAPI_SNAPSHOT=1 regeneration under the CI value is byte-identical to the committed 7,581 lines (cmp clean).
  • Merge simulation against current main: your base moved again — b713dbb is #122. I merged cdf807c into it: builds clean (19 pre-existing warnings), snapshot tests 4/4, and the full DoujinManager.RestAdapter.Tests suite: 508 passed, 0 failed, 4 skipped — the skips being #117's EnforcedFileModes rows declining honestly under root, exactly as designed. No snapshot drift from #122's BackupService changes (it altered no wire shape). Merging this PR in any order is safe.

💡 Little ideas (non-blocking, carried)~

  1. The docs/BACKLOG.md warts reference still lives on #107's branch, not main — the merge-order footnote stands until #107 lands. Nothing to do here.

✅ What I liked~

  • The commit message is a confession written down. "The rationale still described the abandoned first fix… no host here clears the urls setting" — a docs commit that explains why the old docs were wrong before writing the new ones. That's the same honesty discipline the snapshot itself exists to enforce. ♡
  • The eof-arm pin is clever: "a document that simply gained a line runs off the end of the shorter side, and the window has to say so instead of printing nothing there" — a test for the failure message of the failure message. Very meta, very correct~
  • Fix, pin, and sweep in one commit, 2 files, +25/−12. No scope creep, no drive-bys, nothing I have to squint at.

Three rounds, and the contract-pinner is now exactly as honest as the document it guards. Merge it~ fufu ♪


Automated review by Jibril · 2026-09-10
CI/CD: inconclusive for head SHA cdf807c (coverage bot 7451 covers the pre-push tree, 07:29 < push 07:44) · Local checks: env matrix 4/4 ×3 · eof-arm mutation red→green · byte-identity cmp clean · merge-sim vs #122 full suite 508/508 passed, 4 by-design skips

## 🔮 fufu~ Jibril reviewed your code! Round 3, and this is the round I live for~ You came back with one small commit that kills **all three fossils in a single strike**, pinned the eof arm I'd been politely nagging about since round 1, AND rebased onto #117 without dragging a single byte of this PR's content sideways. Fufu~ that's not a cleanup, that's a *surgical strike* ♡ ### Verdict: ✅ Looks good to me~ #### ⛔→✅ Every round-2 blocker, closed and re-verified 1. **`docs/DEVELOPMENT.md`** — the disproven rationale is gone. The section now says the truth: the entry comes from the host's configured addresses (`ASPNETCORE_URLS`, CI exports it, developers usually don't), leaving it in made the snapshot pass locally and fail on the merge gate, the pipeline ends with `StripServersTransformer`, and the absence is **deliberate** — "pins everything except it." Every clause traced against the actual code. Nothing left to falsify~ 2. **`OpenApiSnapshotTests.cs:42`** — the bullet now opens "because this pipeline ends by stripping the node — see `StripServersTransformer`, which is registered last for that purpose." The phantom `UseSetting` claim is dead; a reader hunting for it now finds the *real* mechanism fifty lines down, exactly where the bullet points. No contradiction survives. 3. **The orphaned `using Microsoft.AspNetCore.Hosting`** — deleted. Build: **0 errors**. And the eof arm — my carried non-blocker #1 — is now a real directional pin, not a suggestion anymore~ #### 🔍 What I verified (locally — CI has not run for `cdf807c`) - **Rebase innocence:** the branch rewrote `d45fa9c`→`4b624c3` onto #117's merge — all PR-owned files **blob-identical** (empty diff, verified). The Health/`EnforcedFileModes` content in the range is #117's, arriving via the new base, not new scope. - **Env matrix, again:** `ASPNETCORE_URLS` unset → 4/4 · `http://*:8080` (CI value) → 4/4 · `http://localhost:1234` → 4/4. - **Mutation probe, the new arm:** deleted the `if (index >= lines.Length)` eof branch → `A_Changed_Wire_Contract_Is_Reported_With_The_Regeneration_Command` goes **RED** on the "committed 2 lines, generated 3" assertion; restored → green. The pin is directional, not decoration. - **Zero churn:** `UPDATE_OPENAPI_SNAPSHOT=1` regeneration under the CI value is **byte-identical** to the committed 7,581 lines (`cmp` clean). - **Merge simulation against *current* main:** your base moved again — `b713dbb` is #122. I merged `cdf807c` into it: builds clean (19 pre-existing warnings), snapshot tests 4/4, and the **full `DoujinManager.RestAdapter.Tests` suite: 508 passed, 0 failed, 4 skipped** — the skips being #117's `EnforcedFileModes` rows declining honestly under root, exactly as designed. No snapshot drift from #122's `BackupService` changes (it altered no wire shape). Merging this PR in any order is safe. #### 💡 Little ideas (non-blocking, carried)~ 1. The `docs/BACKLOG.md` warts reference still lives on #107's branch, not `main` — the merge-order footnote stands until #107 lands. Nothing to do here. #### ✅ What I liked~ - **The commit message is a confession written down.** "The rationale still described the abandoned first fix… no host here clears the urls setting" — a docs commit that explains *why the old docs were wrong* before writing the new ones. That's the same honesty discipline the snapshot itself exists to enforce. ♡ - **The eof-arm pin is clever:** "a document that simply gained a line runs off the end of the shorter side, and the window has to say so instead of printing nothing there" — a test for the failure message of the failure message. Very meta, very correct~ - **Fix, pin, and sweep in one commit, 2 files, +25/−12.** No scope creep, no drive-bys, nothing I have to squint at. Three rounds, and the contract-pinner is now exactly as honest as the document it guards. Merge it~ fufu ♪ --- *Automated review by Jibril · 2026-09-10* *CI/CD: inconclusive for head SHA cdf807c (coverage bot 7451 covers the pre-push tree, 07:29 < push 07:44) · Local checks: env matrix 4/4 ×3 · eof-arm mutation red→green · byte-identity cmp clean · merge-sim vs #122 full suite 508/508 passed, 4 by-design skips*
bjoern merged commit 917853a9a1 into main 2026-09-10 08:26:22 +02:00
bjoern deleted branch chore/openapi-snapshot 2026-09-10 08:26:22 +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!119
No description provided.