diff --git a/serverAppConfigs-docs/Dankserver/Jellyfin.md b/serverAppConfigs-docs/Dankserver/Jellyfin.md index e5435b8..0754b68 100644 --- a/serverAppConfigs-docs/Dankserver/Jellyfin.md +++ b/serverAppConfigs-docs/Dankserver/Jellyfin.md @@ -314,8 +314,12 @@ trigger`, then `… wait`. It calls the library settled only on the rule below task seen `Running` (or a run that finished while it watched) first, then `--require-quiet` reads with no scan and no writes — and refuses by name when the scan never started, because a watcher that saw no `Running` would otherwise report -the pre-scan library as the fresh one. `… status --after $(stat -c %Y )` -is the "has the scan reached my moves" answer, read from the task's `EndTimeUtc`. +the pre-scan library as the fresh one. `… status --after ` is the +"has the scan reached my moves" answer, read from the task's `EndTimeUtc` — and that +timestamp only advances when a scan *finishes*, so the epoch must be one your trigger +could not have followed: a journal mtime is already passed by whatever scan ran before +you asked, and bare `status` cannot tell that walk from yours, while `wait` refuses such +an epoch by name. The Plex door is worse still — [[Plex]]. It carries every law below. Hand-written, the door is a `curl -K` on a mode-600 config: the scan key lives in `~/.config/secrets/jellyfin` (mode 600; the same diff --git a/serverAppConfigs-docs/Dankserver/Plex.md b/serverAppConfigs-docs/Dankserver/Plex.md index c7f08b4..4628b08 100644 --- a/serverAppConfigs-docs/Dankserver/Plex.md +++ b/serverAppConfigs-docs/Dankserver/Plex.md @@ -111,29 +111,36 @@ driver from the host shell; the section keys are `1` Movies, `3` TV Shows, `5` Audiobooks, `6` Music, `11` VODs & Live Events: ```bash -after="$(date +%s)" # the TRIGGER's epoch — never a file mtime scripts/media-refresh-plex.sh trigger --library 1 -scripts/media-refresh-plex.sh status --library 1 --after "$after" +python3 scripts/media-verify.py --db-kind plex \ + --journal /mnt/entropy/.media-campaign/.jsonl # the real proof ``` Exactly ONE section: a refresh costs a full walk of that tree, and refreshing all five for a rename that touched one buys nothing. `trigger` answers `refresh accepted (204)` — `200` when a body is requested — and `000` means PMS is still warming, which the driver retries out loud; a service restart is never the -fix. Progress needs no token and no container: `section_locations.scanned_at` in -the library database carries the same epoch `GET /library/sections` prints as -`scannedAt` (verified 2026-11-04), and that is what `status` compares `--after` -against. +fix. -**The epoch `--after` receives must be the trigger's, not a file's.** `status` answers -one question — has this section scanned since that epoch — so any journal or plan whose -mtime predates the most recent scan makes it report settled the instant you ask. -Measured 2026-11-04: a chain that passed `stat -c %Y ` fell straight through -its wait loop and the verify after it read a database that had not yet processed that -day's renames. The failure mode is a false *success*, never a false alarm, which is -what makes it worth two lines of warning. A purge gate survives it — -`scripts/media-verify.py` asks whether any row still points at a path with no file, and -a scan can clear rows but cannot invent one — but a "the scan is done" claim does not. +**`status` is a census, not a waiter: no epoch makes it watch a scan.** It reads +`section_locations.scanned_at` — the epoch of the last scan PMS has *recorded* for the +section, the same value `GET /library/sections` prints as `scannedAt` (verified +2026-11-04) — and compares it against `--after`. Measured twice on 2026-11-04: a chain +fell through its wait loop in the same second its own trigger was accepted, and a direct +read 30 minutes later still answered `settled` off that one stamp +(`2026-11-04T19:46:59Z`, for a request accepted at `19:46:58Z`) while the walk ran. A +journal's mtime fails, and so does the trigger's own epoch — the request satisfies the +very door it was asked to wait for. The failure direction is a false *success*, never a +false alarm, which is what makes it worth the warning: ask `status` whether a scan got +recorded, and never let a chain depend on it. + +**The proof that a scan did its work is the journal replayed against the database:** +`scripts/media-verify.py --db-kind plex --journal ` names every row that still +points at a source path (`still at src`) and every entry whose file is physically still +there (`REAL`). Both at zero is what gates a purge, and it needs no settle signal at all +— a scan can clear rows but cannot invent one. Jellyfin's door has no such trap: +`scripts/media-refresh-jellyfin.py wait` watches the scan task itself and refuses by +name when no task reaches `Running`. The driver ships its runner into the container with `podman cp` rather than quoting a command through `podman exec sh -c`, because PMS's HTTPD closes without