docs(plex,jellyfin): the scan doors are not waiters; supersedes b82655d
`status` compares a recorded timestamp with `--after`. Measured 2026-11-04: Plex
answered `settled` in the same second its own trigger was accepted (stamp
19:46:59Z for a request accepted 19:46:58Z) and still answered `settled` from that
one stamp 30 minutes later while the walk ran. So no epoch survives it — a journal
mtime fails and the trigger's epoch fails, because the trigger satisfies the door it
was asked to wait for. That is the correction of b82655d, which prescribed the
trigger's epoch for this door.
Jellyfin's `EndTimeUtc` only advances when a scan finishes, so there the trigger's
epoch is sound and a file mtime is not; its `wait` already refuses an epoch an
earlier task passes. The proof a scan did its work stays `media-verify.py`
replaying the journal: a scan can clear rows but cannot invent one.
This commit is contained in:
1 parent
b82655d30a
commit
146c28da9f
2 files changed
+28
-17
No files matched your search
@@ -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
|
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
|
`--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
|
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 <journal>)`
|
the pre-scan library as the fresh one. `… status --after <the trigger's epoch>` is the
|
||||||
is the "has the scan reached my moves" answer, read from the task's `EndTimeUtc`.
|
"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
|
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
|
config: the scan key lives in `~/.config/secrets/jellyfin` (mode 600; the same
|
||||||
|
|||||||
@@ -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:
|
`5` Audiobooks, `6` Music, `11` VODs & Live Events:
|
||||||
|
|
||||||
```bash
|
```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 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/<journal>.jsonl # the real proof
|
||||||
```
|
```
|
||||||
|
|
||||||
Exactly ONE section: a refresh costs a full walk of that tree, and refreshing all
|
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
|
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
|
`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
|
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
|
fix.
|
||||||
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.
|
|
||||||
|
|
||||||
**The epoch `--after` receives must be the trigger's, not a file's.** `status` answers
|
**`status` is a census, not a waiter: no epoch makes it watch a scan.** It reads
|
||||||
one question — has this section scanned since that epoch — so any journal or plan whose
|
`section_locations.scanned_at` — the epoch of the last scan PMS has *recorded* for the
|
||||||
mtime predates the most recent scan makes it report settled the instant you ask.
|
section, the same value `GET /library/sections` prints as `scannedAt` (verified
|
||||||
Measured 2026-11-04: a chain that passed `stat -c %Y <journal>` fell straight through
|
2026-11-04) — and compares it against `--after`. Measured twice on 2026-11-04: a chain
|
||||||
its wait loop and the verify after it read a database that had not yet processed that
|
fell through its wait loop in the same second its own trigger was accepted, and a direct
|
||||||
day's renames. The failure mode is a false *success*, never a false alarm, which is
|
read 30 minutes later still answered `settled` off that one stamp
|
||||||
what makes it worth two lines of warning. A purge gate survives it —
|
(`2026-11-04T19:46:59Z`, for a request accepted at `19:46:58Z`) while the walk ran. A
|
||||||
`scripts/media-verify.py` asks whether any row still points at a path with no file, and
|
journal's mtime fails, and so does the trigger's own epoch — the request satisfies the
|
||||||
a scan can clear rows but cannot invent one — but a "the scan is done" claim does not.
|
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 <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
|
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
|
quoting a command through `podman exec sh -c`, because PMS's HTTPD closes without
|
||||||
|
|||||||
Reference in new issue
Block a user