Manifest fetch across the configured servers (JR-025 … JR-037)
🏗️ Build Plugin / build (push) Successful in 44s
Latest Release / latest-release (push) Successful in 40s
🧪 Test Plugin / test (push) Successful in 26s

Satisfies JRay-public-server UR-007. Servers are tried in configured order and
the first result clearing the configured tier wins; first-match rather than
best-match because querying every server for every item multiplies egress and
leaks the library to more parties, and the ordering already encodes which
source the admin prefers.

Every server is untrusted, including the pre-configured community one, so a
fetched manifest is re-validated against the same rules the server applies on
upload: envelope version refused if unknown, identifiers format-checked,
windows bounds-checked against the *local* file's runtime, belief bounded to
[0, 1], control and bidi characters refused in names. Responses are capped
while streaming rather than after buffering, since a hostile server can declare
any Content-Length it likes. HTTPS is required away from loopback. A failing
server is skipped with exponential backoff so one dead server cannot stall a
sweep.

The audio-tier offset is applied once, at store time, so stored truth is always
in the local file's own timebase and no read path needs offset awareness.
Windows are shifted, never reshaped — merging adjacent ones would answer "was a
face visible" rather than "was the actor present" (SR-002).

Also records why there is no `exact` tier, which was missing and led me to
re-add one. The file-hash tier is withdrawn on legal grounds: a TMDB id
discloses "some copy of this film", but an OpenSubtitles hash discloses "this
exact release", which turns a catalogue lookup into a release-identification
service and a server's database into a mapping from file fingerprints to the
instances holding them. The reason now lives on MatchTier and in SPEC.md §JR-036,
`TitleQuery` has no VideoHash property so there is nothing to send, and a test
asserts the enum has no Exact member — the spec had still listed `exact` as a
configurable tier, which is what made the removal look like an oversight.

42 tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

TRACES: JR-025, JR-027, JR-028, JR-029, JR-030, JR-031, JR-036, JR-037 | PR-005, PR-006
This commit is contained in:
2026-07-31 10:03:49 +02:00
co-authored by Claude Opus 5
parent 64f549ab35
commit 3d210b5bd3
23 changed files with 1858 additions and 18 deletions
+30 -7
View File
@@ -625,14 +625,37 @@ a rejected upload would otherwise push tens of MiB pointlessly.
### JR-036 — Match tier is the user's dial
The configured minimum tier (`exact` / `audio` / `runtime` / `loose`) gates what
may be stored. A `loose` match — runtimes within ±30 s — is plausibly a different
trim of the same cut, so it is **surfaced as a caveat in the UI**, not applied
The configured minimum tier (`audio` / `runtime` / `loose`) gates what may be
stored. A `loose` match — runtimes within ±30 s — is plausibly a different trim
of the same cut, so it is **surfaced as a caveat in the UI**, not applied
silently. Per JR-010 the tier is recorded with the stored truth, which is what
makes surfacing it possible after the fetch has finished.
**Current:** `MinimumMatchTier` exists in configuration, defaulting to `runtime`.
**Gap:** nothing reads it; no caveat is displayed.
**There is no `exact` tier here, and the plugin sends no `video_hash`.** The
server spec §3 defines `exact` as an equal OpenSubtitles file hash, and it is the
strongest *technical* signal available — it identifies a specific file, so it
cannot produce a false positive. That is exactly why it is withdrawn.
A TMDB id discloses "some copy of this film", which is what a library catalogue
discloses. A file hash discloses **this exact release**, which turns a catalogue
lookup into a release-identification service and turns a server's database into a
mapping from file fingerprints to the instances holding them. That is a far more
specific disclosure than PR-005 permits, and a dataset no volunteer operator
should be asked to hold.
The audio signature is the deliberate replacement: derived from *content*, it
identifies the **cut** rather than the copy, so two different encodes of the same
edit agree. It answers the question the exchange needs — "do these timings apply
to this media?" — without answering the one it must not. `audio` is therefore the
top tier.
A server may still hold hashes contributed by other clients; this plugin simply
never participates, and `MatchTier` has no `Exact` member so no code path can
come to depend on one.
**Current:** `MinimumMatchTier` exists in configuration, defaulting to `runtime`,
and `ManifestExchangeClient` rejects a below-tier match. **Gap:** the caveat is
returned by the fetch endpoint but not yet displayed in the overlay.
---
@@ -696,8 +719,8 @@ test rather than an aspiration. Extraction's counterpart is `IR-005`.
**JR-044 — media shorter than 120 s.** The window underflows, so **no signature
is emitted and no sync offset is applied**. Such items fall back to the runtime
and exact tiers, which is adequate: a 90-second extra is not content whose cut
alignment matters. Both producers must apply the identical rule, or they diverge
tier, which is adequate: a 90-second extra is not content whose cut alignment
matters. Both producers must apply the identical rule, or they diverge
on exactly the short items most likely to be misidentified. Extraction's
counterpart is `IR-007`.