# ADR-003: A vanished file is marked missing, never deleted ## Status Accepted ## Date 2026-10-12 ## Context A library root is not always available. Drives unmount, NFS has hiccups, and people reorganise folders while a scan is running. The obvious behaviour for a scanner is to delete rows whose files are gone. That is also the most destructive thing it could do. ## Decision A scan that cannot find a file marks it `missing = 1` and records `missing_since`. It never deletes. Removal is a separate, explicit, admin-only action. ## Alternatives Considered ### Delete on scan - **Pros:** simple; the database always reflects the disk. - **Cons:** deleting a `media_items` row cascades into `progress`. If a drive is unmounted for an hour, every reading position, audiobook chapter and watch-later marker for that drive is gone permanently. - **Rejected:** it destroys the one thing in the schema that cannot be regenerated. Everything else can be rebuilt by rescanning. ### Delete after a grace period, automatically - **Pros:** reclaims space without anyone thinking about it. - **Cons:** a drive left disconnected for longer than the period loses its history silently. The user gets no warning and no undo. - **Rejected:** the failure mode is silent and irreversible. ### Keep `missing` but never surface it - **Pros:** none. This was the original state. - **Cons:** the worst of both worlds — you cannot see what is missing, and it never goes away. A deleted file was invisible *and* permanent. - **Rejected:** this is the gap this ADR closes. ## Consequences - Reads filter on `missing = 0`, so a missing item disappears from every shelf, list and search result without the row being touched. - `missing_since` is written **only on the transition** into missing, so the age says when the file first vanished rather than when it was last rescanned. It is cleared when a file is restored. - `GET /api/v1/missing` lists what has vanished, scoped to the caller's visible libraries. - `POST /api/v1/libraries/{id}/purge` (admin only) deletes only items that are missing **and** have no present file. It requires an explicit confirmation in the UI that names the count and says progress cannot be regenerated. - The purge cascade — `media_files`, `progress`, `item_people`, `provider_ids`, `metadata_fetch`, `assets`, `collection_items`, `favorites`, `item_views`, `game_saves`, child items via `parent_id` — is enumerated in the migration, so the destructiveness is visible at the point of the decision. - Verified: purging 19 items leaves 18, only the missing one goes, and a non-missing item's files are untouched. A second purge returns 0.