Skip to content

Fix weak-storage recovery and root confirmation - #993

Open
therobbiedavis wants to merge 6 commits into
canaryfrom
bugfix/904-weak-storage-compatibility
Open

therobbiedavis wants to merge 6 commits into
canaryfrom
bugfix/904-weak-storage-compatibility

Conversation

@therobbiedavis

@therobbiedavis therobbiedavis commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

This PR fixes the weak-storage compatibility and recovery problems behind #904 without treating CIFS/NFS-specific behavior as trusted durable authority.

Weak storage is now handled capability-first: Listenarr keeps read/scan/additive publication available where it can prove those operations safe, keeps destructive mutation fail-closed when durable generation evidence is unavailable, and exposes structured recovery information instead of collapsing root-folder confirmation/path changes into a generic blocked state.

The branch also fixes metadata rescans on weak storage, hardens legacy weak-identity handling, adds durable registration-publication rollback/recovery, and preserves physical-generation state correctly across SQLite compensation paths.

Fixes #904.

Changes

Added

  • Structured file-registration recovery status/blockers and retry API.
  • Durable rollback states for uncommitted markerless registration publication.
  • Recovery of anonymous publication journals, including safe adoption or rollback.
  • Metadata-only audiobook-file refresh path that works on weak storage without enrolling physical identity.
  • Shared classification for known-weak Linux physical identities, including generic 0x81 file handles and legacy birth-time-only identities.
  • Atomic physical-generation restore support for compensation/recovery.
  • Native weak-storage metadata-rescan regression coverage and additional recovery/persistence tests.

Changed

  • Root-folder confirmation/relocation now reports the blocking publication operation and uses structured recovery state.
  • Registration recovery now treats Move, Copy, and HardlinkCopy consistently.
  • Weak-storage metadata reads use an operation-local pinned path-only lease when durable file generation is unavailable.
  • Filesystem deletion remains fail-closed for legacy weak identities and falls back to library-only removal where appropriate.
  • Registration publication and recovery lifecycle checks use explicit state predicates rather than enum ordering.
  • Physical-generation rollback preserves raw persisted timestamp/identity values instead of re-enrolling identity through domain setters.
  • Root-folder/storage recovery boundaries stop blocking once publication is completed or rolled back.

Fixed

  • CIFS roots incorrectly remaining stuck behind root-folder confirmation/path-change recovery gates.
  • Metadata rescan throwing PlatformNotSupportedException on weak CIFS storage after application restart.
  • Rollback states being able to acquire an owner in the same state transition.
  • Copy/HardlinkCopy registration publications not participating in all recovery/root blocker paths.
  • SQLite physical-generation rollback failing when persisted observation timestamps reload with DateTimeKind.Unspecified.
  • Legacy weak physical identities accidentally being eligible for destructive authority.
  • Organise selecting the durable protocol when live evidence is durable but the persisted tracked identity is still a known-weak legacy Linux identity.

Testing

Windows/current candidate:

  • Release build: 0 warnings, 0 errors.
  • Focused weak-storage/recovery matrix before final review follow-up: 165 passed, 17 skipped, 0 failed.
  • Full backend suite on the predecessor candidate: 3,101 passed, 223 skipped, 0 failed (3,324 total).
  • Final P1 follow-up focused rename/recovery slice: 50 passed, 2 skipped, 0 failed.
  • dotnet format --verify-no-changes: clean.
  • git diff --check: clean.
  • The final P1 follow-up did not modify EF migration files; cumulative migration changes in this branch were generated with EF tooling.
  • Latest GitHub Actions Run Tests, CodeQL, and PR version-label validation all pass on exact final head 2972b44d41230f5cbcec9adfea7f354bc0611156.
  • Three fresh independent adversarial review passes completed cleanly on that same unchanged final head after the P1 follow-up.

Production image — exact final head:

  • Built the actual repository Dockerfile from exact final commit 2972b44d41230f5cbcec9adfea7f354bc0611156.
  • Image tag: listenarr-904:2972b44d-final.
  • Image/manifest-list digest: sha256:4e728f3de32c4104998a82c1cb7df845080a15407a6e8a50f9e8577a604516a7.
  • Exported image manifest: sha256:a5603b381f24fb6fd4ddd9e01a057c162f64978098458ab627446f79060836a7.
  • Image config: sha256:84b929b2ff777989a029c483b48a3ef4ee969246f586c7130b25a7237f383426.
  • Docker Release backend build completed with 0 warnings and 0 errors; frontend production build/type-check completed successfully.
  • Fresh-container startup completed EF migrations, registration recovery, identity reconciliation, and filesystem startup reconciliation.
  • Readiness returned HTTP 200 with database connected, migrations current, and filesystem ready.
  • /audiobooks was mounted from the real reporter-style CIFS fixture and reported filesystem type smb2 inside the exact final image.
  • Restart against the same persistent config and CIFS volumes again returned HTTP 200 with clean registration/filesystem reconciliation.

Real CIFS/SMB validation:

  • Reporter-style SMB 3.0 + nounix + serverino CIFS mount.
  • Live root classified as Limited / IdentityUnsupported, not falsely Changed.
  • Read/scan enabled; additive publication enabled; destructive mutation disabled; verified-copy retirement available.
  • Native verified multi-file/companion import passed.
  • Native Organise weak-storage contract passed.
  • Two-phase CIFS capture -> real remount -> verify recovery passed 1/1 in both phases.
  • Native weak-storage metadata rescan passed and confirmed /proc/... pinned reads without enrolling physical identity.
  • The same metadata-rescan test passed again in a fresh container after the CIFS volume was released/remounted.

Real NFS validation:

  • NFSv4.2 mounted inside the Linux verifier.
  • Durable directory-identity capability contract passed.
  • Durable identity survived real unmount/remount: capture 1/1, verify 1/1.
  • Real read-only NFS authoritative scan passed without mutating the source.
  • Read-only NFS source -> writable NFS destination production flow passed: capability resolution selected additive Copy + retain source, destination publication succeeded, source remained intact.
  • Writable source -> writable NFS durable production publication passed.
  • Writable source -> writable NFS companion publication through the production resolver passed.

Notes

  • This PR intentionally does not change SABnzbd/Usenet cleanup ownership or adapter behavior; that remains separate work.
  • Generic Linux 0x81 file-handle identity remains compatibility evidence only, never durable destructive authority.
  • Weak storage remains intentionally fail-closed for operations that cannot be proven safe.
  • The local procedural handoff document used during implementation was intentionally not committed.

@therobbiedavis
therobbiedavis requested review from a team and a lite review from Copilot September 17, 2026 15:39
@therobbiedavis therobbiedavis added the patch patch version bump - backward compatible bug fixes label Sep 17, 2026 — with ChatGPT Codex Connector

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 047681f0c3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

It changes core filesystem identity/recovery semantics, adds new persistence structures and migrations, and impacts multiple critical flows (imports, organize, root confirmation), warranting final human verification.

Pull request overview

This PR addresses weak-storage (CIFS/NFS) recovery and root-folder confirmation issues (notably #904) by making filesystem operations capability-first, improving recovery observability, and adding durable rollback / recovery paths for markerless publications and verified organize flows.

Changes:

  • Adds structured file-registration recovery blockers/status + a retry API, and wires blockers into root-folder confirmation/relocation gating.
  • Hardens weak-storage identity handling (including generic Linux 0x81 file handles) to fail-closed for destructive operations while keeping safe read/scan/metadata refresh available.
  • Introduces batch manifests for compatibility publications and a new verified-rename journal + startup reconciliation phase, with additional backend + frontend UX/tests for warnings and recovery.
File summaries
File Description
tests/Features/Infrastructure/Persistence/SqliteMigrationSchemaTests.cs Extends schema assertions for new migration columns/tables.
tests/Features/Infrastructure/Persistence/LibraryFilesystemStartupReconciliationServiceTests.cs Covers new startup reconciliation phase ordering (verified rename).
tests/Features/Infrastructure/Persistence/FileRegistrationRecoveryServiceTests.cs Updates recovery expectations (NeedsAttention / rollback behaviors).
tests/Features/Infrastructure/Persistence/FileRegistrationRecoveryProbeTests.cs Adds probe coverage for Copy/HardlinkCopy + structured blockers.
tests/Features/Infrastructure/Persistence/EfAudiobookFileRepositoryBasePathRegistrationTests.cs Adds coverage for raw physical-generation restore behavior.
tests/Features/Infrastructure/Migrations/MigrationMetadataTests.cs Ensures new EF migrations are discoverable.
tests/Features/Infrastructure/Metadata/Jobs/MetadataRescanWeakStorageTests.cs New regression test for metadata rescan on weak storage.
tests/Features/Infrastructure/Library/Scanning/UnmatchedScanBackgroundServiceTests.cs Updates queue callback to capture warnings.
tests/Features/Infrastructure/Library/Scanning/ScanPathAuthorizationServiceTests.cs Adds legacy-weak identity unsupported test for pinned path-only proof.
tests/Features/Infrastructure/FileSystem/RootFolderStorageHealthResolverTests.cs Adds coverage for legacy weak identity remaining limited.
tests/Features/Infrastructure/FileSystem/RootFolderStorageConfirmationServiceTests.cs Expects structured blocker exception type on confirmation conflicts.
tests/Features/Infrastructure/FileSystem/PinnedAudiobookFileRegistrationLeaseTests.cs Covers OpenForMetadataRead path-only leasing behavior.
tests/Features/Infrastructure/FileSystem/FileMutationJournalStoreTests.cs Adds state-transition guard tests for rollback states.
tests/Features/Infrastructure/FileSystem/DockerStorageCapabilityContractTests.cs Adds native mount identity + remount capability contract tests.
tests/Features/Infrastructure/FileSystem/DirectoryObjectIdentityResolverTests.cs Expands Linux identity classification/weak-evidence coverage.
tests/Features/Infrastructure/FileSystem/CompatibilitySourceCleanupCoordinatorTests.cs Adds manifested-batch behavior coverage for cleanup coordinator.
tests/Features/Infrastructure/Downloads/Processing/DownloadProcessingJobProcessorTests.cs Ensures stable compatibility batch id propagation and retained metadata behavior.
tests/Features/Architecture/BackendArchitectureTests.cs Updates audited “operation namespace” allowlist for new organize prefix.
tests/Features/Application/Audiobooks/RootFolders/RootFolderServiceTests.cs Expects root deletion block message to include operation/state.
tests/Features/Api/Services/WorkerProcessorBoundaryTests.cs Adds Linux-only test for partial unreadable scan paths producing warnings.
tests/Features/Api/Features/Library/RootFoldersControllerTests.cs Adds conflict mapping for identity-unsupported confirmation + warnings passthrough.
tests/Features/Api/Features/Library/LibraryController_DeleteFilesystemTests.cs Adds coverage for legacy-weak identity blocking destructive deletes.
tests/Common/PlatformFactAttributes.cs Adds native mount/remount Fact attributes used by capability contract tests.
listenarr.infrastructure/Persistence/VerifiedFileRenameRecoveryService.Probes.cs New probe helpers for verified-rename batch validation/ownership checks.
listenarr.infrastructure/Persistence/Repositories/EfAudiobookFileRepository.PhysicalGeneration.cs Adds atomic restore APIs and tracked-entry synchronization.
listenarr.infrastructure/Persistence/Repositories/EfAudiobookFileRepository.MetadataRefresh.cs New metadata-only refresh persistence method (no identity writes).
listenarr.infrastructure/Persistence/Repositories/EfAudiobookFileRepository.BasePathRegistration.cs Adds restore-with-basepath transactional path.
listenarr.infrastructure/Persistence/Migrations/ListenArrDbContextModelSnapshot.cs Updates snapshot for new journal/table and manifest columns.
listenarr.infrastructure/Persistence/Migrations/20260901142347_AddVerifiedFileRenameJournal.cs New migration creating VerifiedFileRenameJournals table.
listenarr.infrastructure/Persistence/Migrations/20260830025709_AddCompatibilityBatchManifest.cs New migration adding batch-manifest columns to compatibility journals.
listenarr.infrastructure/Persistence/ListenArrDbContext.cs Adds DbSet for VerifiedFileRenameJournals.
listenarr.infrastructure/Persistence/LibraryFilesystemStartupReconciliationService.cs Runs verified-rename recovery during startup reconciliation.
listenarr.infrastructure/Persistence/FileRenameRecoveryProbe.cs Treats verified-rename journals as rename blockers.
listenarr.infrastructure/Persistence/FileRegistrationRecoveryService.RepairState.cs Uses explicit lifecycle predicates and improves pending messaging.
listenarr.infrastructure/Persistence/FileRegistrationRecoveryService.Receipts.cs Broadens receipt query predicate (publication owner).
listenarr.infrastructure/Persistence/FileRegistrationRecoveryService.Protocol.cs Scopes protocol enforcement optionally to a specific operation id.
listenarr.infrastructure/Persistence/FileRegistrationRecoveryProbe.cs Adds Copy/HardlinkCopy + structured boundary blocker reporting.
listenarr.infrastructure/Persistence/Configurations/VerifiedFileRenameJournalConfiguration.cs New EF entity configuration for verified-rename journal.
listenarr.infrastructure/Persistence/Configurations/CompatibilityFilePublicationJournalConfiguration.cs Adds max length config for new manifest digest column.
listenarr.infrastructure/Persistence/CompatibilityFilePublicationRecoveryService.cs Recovers manifested batches via coordinator at startup.
listenarr.infrastructure/Metadata/Jobs/MetadataRescanService.cs Uses metadata-only refresh when durable generation is unavailable.
listenarr.infrastructure/Library/Scanning/UnmatchedScanBackgroundService.cs Preserves partial results + emits warnings and warningCount.
listenarr.infrastructure/Library/Scanning/ScanPathAuthorizationService.cs Keeps pinned path-only proof for legacy weak identity when still unsupported.
listenarr.infrastructure/Library/Moving/RootFolderRelocationService.MetadataRepair.cs Treats Copy/HardlinkCopy like Move for boundary blocking.
listenarr.infrastructure/Library/Moving/RootFolderRelocationService.ExternalRecovery.cs Includes operation/state in boundary conflict messages.
listenarr.infrastructure/Library/Moving/AudiobookFilesystemDeleteService.GenerationProof.cs Blocks cleanup completion for known-weak identities.
listenarr.infrastructure/Library/Moving/AudiobookFilesystemDeleteService.cs Blocks destructive deletion for known-weak or missing durable identity.
listenarr.infrastructure/FileSystem/RootFolderStorageHealthResolver.cs Treats legacy weak + unsupported current identity as “Limited/Unsupported”.
listenarr.infrastructure/FileSystem/RootFolderStorageConfirmationService.cs Adds structured registration blocker probe + throws dedicated exception.
listenarr.infrastructure/FileSystem/PinnedDirectoryCreation.LinuxObjectIdentity.cs Separates weak generic FID evidence and probes for stronger handle if possible.
listenarr.infrastructure/FileSystem/PinnedDirectoryCreation.LinuxIdentityCandidates.cs Adds durable-vs-weak identity predicates and legacy weak candidate creation.
listenarr.infrastructure/FileSystem/PinnedDirectoryCreation.Hierarchy.cs Exposes legacy weak directory identity candidates for classification.
listenarr.infrastructure/FileSystem/PinnedAudiobookFileRegistrationLease.cs Adds OpenForMetadataRead with safe path-only fallback on weak storage.
listenarr.infrastructure/FileSystem/FileMutationJournalStore.AdvanceState.cs Adds rollback state validation and terminality enforcement.
listenarr.infrastructure/FileSystem/FileMover.MarkerlessRegistrationMove.cs Prevents source retirement for rollback/rolled-back/attention states.
listenarr.infrastructure/FileSystem/FileMover.MarkerlessRegistration.cs Uses lifecycle predicates and supports rollback state handling.
listenarr.infrastructure/FileSystem/FileMover.CompatibilityRegistration.cs Persists compatibility batch manifest fields in claim.
listenarr.infrastructure/FileSystem/DirectoryObjectIdentityResolver.cs Classifies persisted generic FID identities as legacy weak; requires durable candidate for authority.
listenarr.infrastructure/FileSystem/CompatibilitySourceCleanupCoordinator.QuarantineCleanup.cs Extracts quarantine cleanup helper into partial.
listenarr.infrastructure/FileSystem/CompatibilitySourceCleanupCoordinator.cs Adds “all completed” disposition resolution + manifest gating.
listenarr.infrastructure/FileSystem/CompatibilitySourceCleanupCoordinator.BatchManifest.cs New manifest validation/match logic + retain-only completion helper.
listenarr.infrastructure/FileSystem/CompatibilityFilePublicationJournalStore.cs Supports rebinding legacy retained attempts into manifested batches.
listenarr.infrastructure/Downloads/Processing/DownloadProcessingJobProcessor.cs Introduces stable CompatibilityBatchId derivation and always-populated import options.
listenarr.infrastructure/DependencyInjection/Library/LibraryRegistrationExtensions.cs Registers verified rename coordinator + recovery service in DI.
listenarr.domain/Downloads/VerifiedFileRenameJournal.cs New domain journal model and state machine for verified organize.
listenarr.domain/Downloads/ImportResult.cs Adds optional SourcePath to Skipped results + sets disposition accordingly.
listenarr.domain/Downloads/FileMutationJournal.cs Adds rollback states + lifecycle helper predicates.
listenarr.domain/Downloads/CompatibilityFilePublicationJournal.cs Adds persisted manifest columns.
listenarr.application/Downloads/Import/DownloadImportService.Quality.cs Extracts best-existing-quality resolution helper.
listenarr.application/Downloads/Import/DownloadImportService.DirectoryOwnership.cs Plumbs optional batch manifest into publication plan resolution.
listenarr.application/Downloads/Import/DownloadImportService.cs Generates compatibility batch manifest and records SourcePath on skip results.
listenarr.application/Downloads/Import/DownloadImportService.Coordination.cs Uses caller-provided CompatibilityBatchId when available.
listenarr.application/Downloads/Contracts/IFilePublicationCapabilityResolver.cs Extends FilePublicationPlan with manifest fields + helper method.
listenarr.application/Downloads/Contracts/IFileMover.cs Adds rollback API for uncommitted anonymous registration publications.
listenarr.application/Downloads/Contracts/IDownloadImportService.cs Adds CompatibilityBatchId to import options.
listenarr.application/Downloads/Contracts/CompatibilityBatchManifest.cs New manifest struct for expected-member set hashing/validation.
listenarr.application/Audiobooks/RootFolders/RootFolderService.cs Improves delete-block errors with operation/state details.
listenarr.application/Audiobooks/Renaming/RenameService.VerifiedExecution.cs Adds verified organize execution planning and source retirement completion.
listenarr.application/Audiobooks/Renaming/RenameService.Rollback.cs Integrates verified-rename rollback path.
listenarr.application/Audiobooks/Renaming/RenameService.Execution.cs Uses verified transaction coordinator when durable authority is unavailable.
listenarr.application/Audiobooks/Renaming/RenameService.cs Wires verified rename planning/execution into rename flow.
listenarr.application/Audiobooks/Renaming/RenameModels.cs Tracks verified-rename lease on result items.
listenarr.application/Audiobooks/Jobs/UnmatchedScanQueueService.cs Adds warnings to queue job model + update API.
listenarr.application/Audiobooks/Files/AudiobookFileService.PhysicalGeneration.cs Uses snapshot-based restore APIs for rollback preservation.
listenarr.application/Audiobooks/Files/AudiobookFileService.MetadataRefresh.cs New metadata-only refresh operation with strict ownership snapshot checks.
listenarr.application/Audiobooks/Contracts/Repositories/IAudiobookFileRepository.cs Adds metadata refresh + physical generation restore APIs and snapshot types.
listenarr.application/Audiobooks/Contracts/PhysicalObjectIdentitySafety.cs New weak-identity classifier used to block destructive authority.
listenarr.application/Audiobooks/Contracts/IVerifiedFileRenameTransactionCoordinator.cs Adds verified batch manifest model and lease/coordinator interfaces.
listenarr.application/Audiobooks/Contracts/IRootFolderStorageConfirmationService.cs Adds RootFolderRecoveryBlockedException carrying structured blocker info.
listenarr.application/Audiobooks/Contracts/IFileRegistrationRecovery.cs Adds structured blocker/status models + retry API to recovery service contract.
listenarr.application/Audiobooks/Contracts/IAudiobookFileService.cs Adds metadata-only refresh API.
listenarr.api/Features/Library/RootFoldersController.Mapping.cs Adds structured registration recovery conflict payload builder.
listenarr.api/Features/Library/RootFoldersController.cs Maps identity-unsupported + structured recovery blocker exceptions; includes warnings in unmatched results.
listenarr.api/Features/Library/LibraryDeleteWorkflow.cs Treats known-weak identity as unverified delete source.
listenarr.api/Features/Library/FileRegistrationRecoveryController.cs New API endpoint to retry file-registration recovery by operation id.
fe/src/views/library/LibraryImportView.vue Displays scan warnings in Library Import UI.
fe/src/views/library/AudiobookDetailView.vue Refreshes audiobook view after scan completion and improves scan failure toasts.
fe/src/types/index.ts Adds warnings fields to unmatched scan response types.
fe/src/stores/libraryImport.ts Stores and displays scan warnings from API responses.
fe/src/components/feedback/UnmatchedFilesModal.vue Shows warnings; adds polling fallback when SignalR completion is missed.
fe/src/tests/UnmatchedFilesModal.spec.ts Covers cached warnings display and polling-to-completion behavior.
fe/src/tests/libraryImport.store.spec.ts Asserts warnings are persisted into store state.
fe/src/tests/AudiobookDetailView.spec.ts Updates scan completion handling via store-driven updates.
Review details

Files not reviewed (1)

  • listenarr.infrastructure/Persistence/Migrations/20260830025709_AddCompatibilityBatchManifest.Designer.cs: Generated file
  • Files reviewed: 120/122 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread listenarr.infrastructure/Persistence/FileRenameCommitStore.cs
Comment thread listenarr.infrastructure/Persistence/FileRenameCommitStore.cs
Comment thread listenarr.infrastructure/Persistence/FileRenameCommitStore.cs
Comment thread listenarr.infrastructure/Persistence/FileRenameCommitStore.cs
Comment thread listenarr.infrastructure/Persistence/FileRenameCommitStore.cs
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 18, 2026
An independent reviewer went over the two commits before this one. Nothing it found was a
defect in shipped behaviour, but four things were wrong enough to fix before any of this is
offered anywhere, and one of them is a correction to the commit before last.

**The OpenAPI schema moved and nothing said so.** Three representations were checked for
"the stored form does not change" and there is a fourth. BlocklistController declares
ProducesResponseType(typeof(IReadOnlyList<BlockedRelease>)), and Swashbuckle builds a schema
from the CLR shape without reading [JsonConverter], so ReleaseIdentifier was published as an
object with four read-only accessors while the wire carried a string. A spec that contradicts
the wire is worse than either a clean break or no change: a generated client models an object
and receives a string. ListenarrSwaggerRegistration now maps the type, and a test asserts the
schema stays a string and that no schema of its own appears for it. Measured: removing the
map fails that test and nothing else.

**Two Sonarr line citations did not check out.** Both in the NormalizeInfoHash comment, both
inherited from the commit that wrote it. Checked against Sonarr@develop today:
TorrentClientBase.cs:224 is the MonoTorrent call, not :233, which is
EnsureReleaseIsNotBlocklisted; and the call reads MagnetLink.Parse(magnetUrl).InfoHash.ToHex(),
not InfoHashes.V1OrV2.ToHex(), which is the v2 API that Radarr and Readarr use here and Sonarr
does not. TorrentRssParser.cs:85 was right. A maintainer who opened :233 would have found an
unrelated line.

**FromStorage refused an empty key, which is the failure its own comment argued against.**
The comment says a malformed row must not throw on the read path, because the read sits on the
search path and one bad row would take out searching for that book. It then threw on an empty
string, and the column is only NOT NULL, which an empty string satisfies. Measured before the
fix: one row with an empty column made GetForAudiobookAsync throw for that book. It is lenient
now, and the guards that matter are on the write, where Key throws rather than storing a
default and BlockAsync refuses an empty identifier. IsEmpty covers the empty string as well as
the default.

**The JSON converter could not read back what it wrote.** Write went through ToString, so a
default serialised as ""; Read called FromStorage and refused it. No production path
deserialises a BlockedRelease today, which is why it would have been found late. Both halves
agree now, with a test and a control.

**The widened prefix scan added no coverage at all.** The previous commit extended the
inline-literal scan to ReleaseIdentifier.cs and said a bare literal would now be caught there.
ReleaseIdentifier.cs contains no return statement anywhere, every member being
expression-bodied, and the pattern was anchored on `return`. Measured by the reviewer: adding
ForNzbId(id) => new("nzbid:" + id) to that file left all 25 golden-vector tests green. The
pattern no longer anchors on a statement; it matches a literal that ENDS at the colon, which
is what separates a key prefix from "urn:btih:" in the same file. The reflection guard beside
it now reads both types and non-public constants, because prefix application moved and a
private const on the new type was invisible to both guards. Measured both escapes: each now
fails, and the sample in the test carries the must-match and must-not-match shapes.

**Correction to f0e192c.** Its message justifies the change as stopping the branch touching
PR Listenarrs#993's files. That is not true and the reason is worth recording, because it invalidated a
check the whole day was run against. `gh pr view 993 --json files` returns 100 paths; the PR
has 122, GraphQL's files connection caps at 100, and gh prints no warning. The 22 missing paths
were all under tests/Features/Infrastructure/**, and one of them is
SqliteMigrationSchemaTests.cs, which every branch adding a migration must edit because
MigrationHistory_ContainsOnlyRetainedRepairsAndConsolidatedPrMigrationAfterCanary asserts an
exact ordered array of post-canary migration ids. Measured with `gh api --paginate`: the real
overlap is two files, and it was three before f0e192c. Measured with `git merge-tree` against
Listenarrs#993's head: the DbSet line auto-merged, so removing it changed the conflict set not at all.
The single conflict is SqliteMigrationSchemaTests.cs, before and after.

f0e192c stands on its own merits instead, which are real: the entity is registered from its
configuration class, the table name is now explicit and tested, and the file every table-adding
pull request appends to is left alone. No force-push to reword it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

patch patch version bump - backward compatible bug fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug Report: Root folder confirmation permanently fails on CIFS mount despite healthy filesystem state

2 participants