test: pin the served OpenAPI document with a committed snapshot #119
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "chore/openapi-snapshot"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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 — plusOpenApiSnapshotTests, 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, itsProxyAwareServerTransformer, andApplicationNametaken from the Server assembly soinfo.titlereadsDoujinManager.Server | v1and not the runner'stesthost | v1), maps the endpoint files inProgram.csorder, callsStartAsync()on the TestServer, resolves the keyedIOpenApiDocumentProvider("v1")and serialises withOpenApiJsonWriter. Output is normalised to LF with a trailing newline, so the file is the same bytes whichever platform regenerated it. It runs insideverify.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:
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_Filereflects over the RestAdapter assembly and fails naming anyMap*Endpointsfile 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, theProduces<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 /versionis mapped inline inProgram.cs, so no test host reaches it (ADR 0021's self-declared blind spot —/healthalready moved into an endpoint file); and there is noserversentry, because the document is resolved outside a request andProxyAwareServerTransformerno-ops on a nullHttpContext.Verification
DoujinManager.RestAdapter.Tests— 497 passed, 0 failed. Both known flakes passed.StatsDto.Storage's[JsonPropertyName]fromstoragetostorageUsage→ the snapshot test went red at line 6923 with both sides of the diff and the regeneration command. Reverted.MapStatsEndpointsfromMappedEndpointFiles→Snapshot_Maps_Every_Endpoint_Filewent red naming it, and the snapshot test went red on the 160 missing lines. Restored./openapi/v1.jsonbyte 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 andGenerateDocumentationFile;.Produces<Stream>on the three image endpoints with an ADR 0021 amendment, since the ADR prescribesFileResultand that was measured to be worse on this stack (an object schema plusEntityTagHeaderValue/StringSegmentdragged intocomponents); the undeclared 413 and 409; movingGET /versionintoHealthEndpoints.cs; the Dart-sideopenapi_contract_test.dartthis snapshot exists to enable; and pinning pastMicrosoft.OpenApi2.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
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>🔮 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
MappedEndpointFilesmatchesProgram.csexactly — all 8 entries, in mapping order, against the realProgram.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, singleIEndpointRouteBuilderparameter) matches exactly the 8 real methods and nothing else in the RestAdapter assembly. A forgotten ninth endpoint file cannot slip through.info.titleat line 4 → red immediately with precise first-difference reporting: line number, both sides' context windows, line counts, and the exactUPDATE_OPENAPI_SNAPSHOT=1regeneration command. Restored → green. Directional, not a tautology. The failure message is genuinely a review tool.od-verified), and.gitattributesenforceseol=lffor*.jsonanyway — belt and suspenders~/versionreally is mapped inline inProgram.cs(ADR 0021's own amendment names it as the last one outside an endpoint file), andProxyAwareServerTransformerreally does no-op on a nullHttpContext(checked the transformer:if (httpContext is not null)guards the whole body), so a request-less resolve genuinely cannot produce aserversentry. Both gaps are written into the test's doc comment andDEVELOPMENT.md.info.title=DoujinManager.Server | v1(theApplicationNametrick works — notesthostin sight), 54 paths, no/version, noservers— exactly as documented.docs/BACKLOG.mdon #107's branch carries them all (lines 113–139): the bearer scheme, the operationIds/summaries, the undeclared 413/409, the/versionmove, theMicrosoft.OpenApiNU1903 pin — each phrased as "regenerate the snapshot" work. The ADR 0021FileResultamendment you deferred is real too: the ADR prescribesFileResult, your measurement saysProduces<Stream>is better on this stack — amending the ADR in the fix PR rather than smuggling the contradiction in here is the right call.ci.ymlrunsCOVERAGE=1 scripts/verify.sh backend, which is plaindotnet testover the whole backend — so the new gate runs in CI with zero script changes, exactly as claimed.💡 Little ideas (non-blocking)~
docs/BACKLOG.mditems", 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~OpenApiSnapshotTests.cs:214— the(end of file)arm ofAppendWindow(when one document is shorter than the diff index) has no direct unit test inA_Changed_Wire_Contract_...— yourMapStatsEndpoints-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.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-freeConfigureServerhelper. I'll be watching~ ♡✅ What I liked~
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.ResolveSnapshotPathwalking up toDirectory.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)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.
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/healthtest in #117, which is a poor look for a PR whose whole job is pinning a contract.Reproduced locally before fixing: with
ASPNETCORE_URLSunset it passes, withhttp://*:8080it fails at line 7 exactly as CI reported.The PR body's explanation was also wrong. It said there was no
serversentry because the document is resolved outside a request andProxyAwareServerTransformerno-ops on a nullHttpContext. 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. Aserversentry says where a server happens to run, not what it serves, and behind a proxyProxyAwareServerTransformerrewrites 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:
ASPNETCORE_URLSASPNETCORE_URLS=http://*:8080(the CI value)ASPNETCORE_URLS=http://localhost:1234Still directional — not quietly weakened into passing. With the CI value set, renaming
StatsDto.Storage's[JsonPropertyName]fromstoragetostorageUsagereddens it at line 6923 naming the property, then restores green. FullDoujinManager.RestAdapter.Testsunder the CI value: 497/497.The other four failures are not this PR's.
HealthEndpointIntegrationTestsfails at uid 0 because #112's test usesFile.SetUnixFileMode, which root ignores — its honesty guard is correctly refusing to let a vacuous test pass. #117 fixes that, and every PR branched off currentmainwill show those four until it merges. Rebase or re-run this one after #117 lands.d45fa9c2aa4b624c3e2d🔮 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
d45fa9ccorrected 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~
docs/DEVELOPMENT.md:120-123— still carries the disproven rationale, verbatim: "there is noserversentry because the document is resolved outside a request (ProxyAwareServerTransformerno-ops on a nullHttpContext; 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 andASPNETCORE_URLS=http://*:8080set, the snapshot gains aserversblock 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
serversentry because .NET derives it from host addresses (ASPNETCORE_URLS), so the test pipeline ends withStripServersTransformerand the snapshot pins everything except that node.OpenApiSnapshotTests.cs:43— the corrected bullet opens with a claim about code that does not exist: "There is noserversentry, because this host clears theurlssetting." Nothing in this host clears anyurlssetting — that was your abandonedUseSettingattempt, and your ownStripServersTransformerremarks say clearing it "is not enough." A reader will go hunting forUseSetting/WebHostDefaultscode and find nothing. It also contradicts the transformer's remarks fifty lines down.Fix: one clause — "because the pipeline ends by stripping the
serversnode" (the rest of the bullet, the ASPNETCORE_URLS mechanism, is correct and lovely).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
d45fa9cyet)ASPNETCORE_URLSunset → 4/4 ·http://*:8080(the CI value) → 4/4 ·http://localhost:1234→ 4/4. Exactly your table.StripServersTransformerregistration → 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.UPDATE_OPENAPI_SNAPSHOT=1regeneration under the CI value is byte-identical to the committed 7,581 lines (cmpclean). The fix changes what the test ignores, not what it pins.ProxyAwareServerTransformerwith "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 populatesServersfrom the host addresses.Servers?.Clear()semantics: empirically safe — empty collection serializes to nothing (the committed file has noserverskey, and regeneration matches it byte for byte).mainis nowe0ac19d— #117 merged, so the fourHealthEndpointIntegrationTestsuid-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)~
AppendWindowend-of-file arm still has no direct unit test — still purely additive, still optional.docs/BACKLOG.mdwarts reference still lives on #107's branch, notmain— the merge-order footnote stands until #107 lands.✅ What I liked~
urlssetting 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. ♡/healthenvironment 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-identitycmpclean · orphaned-using build probe 0 errorsSummary
Summary
Coverage
DoujinManager.ApplicationCore - 90.9%
DoujinManager.Infrastructure - 96.4%
on
pshot
Series
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__CoverImageHelper
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__PrimaryTitleHelper
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__RolledBackImageCleanup
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__UploadChapterValidator
DoujinManager.RestAdapter - 92.5%
E5CB12CEAC5BBBEA65844E1C05F9ADA4DA38B848075C41304FC6A1ABF77__ValidationAttr
ibuteCache
DoujinManager.Server - 49.6%
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 withStripServersTransformer, 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 theurlssetting" is gone. Nothing in the host clears any setting; that was theUseSettingattempt the transformer's own remarks say is insufficient. The bullet now attributes the absence to the stripper, and keeps theASPNETCORE_URLSmechanism, which was correct.3. The orphaned
using Microsoft.AspNetCore.Hosting;is removed. Build 0 errors —UseTestServercomes fromMicrosoft.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 theMapStatsEndpoints-removal probe.Verification — the environment matrix again, since that is the property at stake:
ASPNETCORE_URLSASPNETCORE_URLS=http://*:8080Nothing false left:
grepforMicrosoft.AspNetCore.Hosting, for "clears theurlssetting", and for "no-ops on a null" across the test file andDEVELOPMENT.mdall 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.🔮 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:
ASPNETCORE_URLS→ 4/4;http://*:8080(the CI value) → 4/4;http://localhost:1234→ 4/4. (One run of mine went red mid-matrix —OutOfMemoryExceptioninConsoleLoggerProvider'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~)StripServersTransformerunder 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. ♪Program.csuntouched — grep-verified),v1.jsonis byte-identical to the round-1 snapshot (correct: on an address-less host stripping changes nothing), and the rebase ontoe0ac19dcarried your two commits through clean — the PR files are byte-identical to the pre-rebase branch.HealthEndpointIntegrationTestsunder 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
serversentry — 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~
OpenApiSnapshotTests.cs:43–44— the corrected docblock says there is noserversentry "because this host clears theurlssetting." It doesn't. Nothing in this file clearsurls— that was the abandoned attempt (UseSetting), and it isn't in the final diff. The host strips the node, viaStripServersTransformer. Worse, forty lines down, the transformer's own remarks say "Clearing theurlssetting 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_URLSattribution, drop the phantomurls-clearing.docs/DEVELOPMENT.md:126–128— still ships the original, retracted explanation: noserversentry "because the document is resolved outside a request (ProxyAwareServerTransformerno-ops on a nullHttpContext; 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_URLSdiffers per environment; the deployed server serves its own address there.OpenApiSnapshotTests.cs:10—using Microsoft.AspNetCore.Hosting;is dead. I deleted it and rebuilt: zero errors. It's a fossil of the abandonedUseSetting/WebHostDefaultsattempt. 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)~
OpenApiSnapshotTests.cs:216–222— the(end of file)arm ofAppendWindow(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 ofAssert, purely additive. (Round-1 leftover, still open.)IntegrationTestBase,EndpointResponseTypeTests, this) — still acknowledged and still fine. But fufu~ a fourth copy is where I stop smiling~✅ What I liked~
UseSettingalternative 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.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 threeASPNETCORE_URLSenvironments; 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!
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
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 withStripServersTransformer, and the absence is deliberate — "pins everything except it." Every clause traced against the actual code. Nothing left to falsify~OpenApiSnapshotTests.cs:42— the bullet now opens "because this pipeline ends by stripping the node — seeStripServersTransformer, which is registered last for that purpose." The phantomUseSettingclaim is dead; a reader hunting for it now finds the real mechanism fifty lines down, exactly where the bullet points. No contradiction survives.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)d45fa9c→4b624c3onto #117's merge — all PR-owned files blob-identical (empty diff, verified). The Health/EnforcedFileModescontent in the range is #117's, arriving via the new base, not new scope.ASPNETCORE_URLSunset → 4/4 ·http://*:8080(CI value) → 4/4 ·http://localhost:1234→ 4/4.if (index >= lines.Length)eof branch →A_Changed_Wire_Contract_Is_Reported_With_The_Regeneration_Commandgoes RED on the "committed 2 lines, generated 3" assertion; restored → green. The pin is directional, not decoration.UPDATE_OPENAPI_SNAPSHOT=1regeneration under the CI value is byte-identical to the committed 7,581 lines (cmpclean).b713dbbis #122. I mergedcdf807cinto it: builds clean (19 pre-existing warnings), snapshot tests 4/4, and the fullDoujinManager.RestAdapter.Testssuite: 508 passed, 0 failed, 4 skipped — the skips being #117'sEnforcedFileModesrows declining honestly under root, exactly as designed. No snapshot drift from #122'sBackupServicechanges (it altered no wire shape). Merging this PR in any order is safe.💡 Little ideas (non-blocking, carried)~
docs/BACKLOG.mdwarts reference still lives on #107's branch, notmain— the merge-order footnote stands until #107 lands. Nothing to do here.✅ What I liked~
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