Merge main into blitz/subtitles
Feedback pass 2 and the size-band work landed on main while this branch was finishing. Brings them in ahead of the merge back. # Conflicts: # crates/arr-api/src/movies.rs # crates/arr-api/src/state.rs # crates/arr-daemon/src/main.rs # web/src/main.ts
This commit is contained in:
@@ -199,6 +199,26 @@ fake profile per root and ignoring whatever it sends.
|
||||
Per-title `overrides` relax the root policy for one title. Same mechanism in
|
||||
both directions — `only_4k` tightens, `allow_english_audio` loosens.
|
||||
|
||||
**Stored verdicts follow the effective policy.** A release's verdict is
|
||||
stamped by the search that found it, and both §9.3's deck and the daemon's
|
||||
manual-grab gate read that stored column. So the four operator actions that
|
||||
change a title's effective policy — editing its overrides, moving it to a
|
||||
root with a different policy, pointing a root at a different policy, and
|
||||
editing the contents of a policy some root points at — re-derive the stored
|
||||
verdicts of everything they touch, inline in the same request. The operator
|
||||
is never left reading a verdict computed under a policy that no longer
|
||||
applies.
|
||||
|
||||
The fourth is the widest: a repoint moves one library, a policy edit moves
|
||||
every library sharing the policy. Inline still holds there. Re-evaluation is
|
||||
pure and in-memory, a row whose verdict does not move is not rewritten, and
|
||||
the ceiling is the whole database rather than something that grows with the
|
||||
number of roots — roots partition titles, and a title has exactly one root.
|
||||
Measured over 2000 titles and 10 000 stored releases split across two roots
|
||||
sharing one policy: 0.36 s when the edit moves no verdict, 2.7 s in the
|
||||
pathological case where it moves all 10 000. A rename changes no rule and
|
||||
re-derives nothing.
|
||||
|
||||
### 5.2 Language
|
||||
|
||||
Requires a concept the release name does not carry: the **original language of
|
||||
@@ -307,6 +327,26 @@ measured by its full size. That is the behaviour today, and it makes a pack look
|
||||
oversized rather than undersized — it fails toward rejecting a good pack rather
|
||||
than grabbing a bad one, and the next search after a refresh has the real count.
|
||||
|
||||
**A band describes a rate, not a fixed size per episode.** The shipped values
|
||||
are read against a **reference runtime of 45 minutes**: a 1080p target of 2 GiB
|
||||
means 2 GiB per 45 minutes of episode. Before the per-episode figure is compared
|
||||
against them, a band's floor and target are both scaled by
|
||||
`runtime / 45`, where `runtime` is the series' minutes-per-episode from TMDB
|
||||
metadata. For a typical drama around 45 minutes the shipped numbers keep exactly
|
||||
their current meaning; a 22-minute show is judged against roughly half the floor
|
||||
and half the target instead of being rejected for weighing half of what an hour
|
||||
of video weighs. The shipped band values themselves do not change — the
|
||||
reference runtime is chosen so they do not have to.
|
||||
|
||||
**A missing or zero runtime is the reference runtime.** When metadata carries no
|
||||
per-episode runtime, or carries zero, the scale factor is 1 and the band applies
|
||||
unscaled — exactly today's behaviour. The rule does not guess a duration, for
|
||||
the same reason the unknown episode count does not guess a number.
|
||||
|
||||
**Movies are not scaled.** This applies to episodes only. A movie's bands are
|
||||
already tuned against feature length, so its floor and target keep their current
|
||||
meaning regardless of the movie's own runtime.
|
||||
|
||||
Source tier (`Remux > BluRay > WEB-DL > WEBRip > HDTV`) survives as a small
|
||||
tiebreaker. Seeders are log-scaled and small: enough to complete, past that it
|
||||
does not matter. Telesync, CAM and screener are **hard filters**, not low
|
||||
@@ -358,6 +398,46 @@ A policy violation found by `ffprobe` is not one thing.
|
||||
|
||||
Neither deletes the torrent. See §7.3.
|
||||
|
||||
**Two hard failures make a decision, and only for 30 days.** A movie, an
|
||||
episode or a season enters the needs-a-decision queue (§9.5) when two grabs
|
||||
against *different* releases hard-failed on it, and both of those failures
|
||||
happened within the last 30 days. One bad torrent is not a decision — a
|
||||
release that hard-failed is blacklisted (§6.3) and the next candidate is
|
||||
grabbed, which is the system working.
|
||||
|
||||
The window runs from the failure, not the grab. The two are usually minutes
|
||||
apart, but a torrent can sit stalling on a dead swarm for five weeks before
|
||||
`ffprobe` finally condemns it — and that failure is fresh evidence the target
|
||||
is broken now, not history. Measured from the grab it would be born outside
|
||||
the window and a genuinely broken target could never surface. So `grabs`
|
||||
records `failed_at` alongside `grabbed_at`, and the window reads it.
|
||||
|
||||
The window is what lets the queue be emptied. Nothing clears a `grabs` row, so
|
||||
without it the queue only ever grows and the one season that wants attention
|
||||
sits behind eight that were dealt with months ago. It is the queue's version of
|
||||
§6.2's "it never gives up entirely, it goes quiet": a target the operator has
|
||||
dealt with stops producing failures and drops out once the last one ages past
|
||||
30 days, while a target that is still broken keeps producing them — the pack
|
||||
guard retries at worst weekly (§6.2) — and stays queued for exactly as long as
|
||||
it is genuinely broken. Nothing is dismissed by hand and no acknowledgement
|
||||
state is stored, so there is no second thing to keep correct.
|
||||
|
||||
**Only a target still waiting for a file is queued.** The count and the
|
||||
window already express one rule — bounded attention — and this is another
|
||||
face of it: a failure history queues a target only while that target still
|
||||
has a gap to fill. A movie or an episode is queued while it is `wanted` and
|
||||
not `available`. A season holds no intent of its own (§4.1), so it is queued
|
||||
while at least one of its episodes is still `wanted` and not `available`. A
|
||||
season pack that hard-failed twice, fell back to per-episode grabbing
|
||||
exactly as §6.2 says it should, and was then fully acquired leaves at once
|
||||
rather than waiting out the 30 days — that is the system working, not a
|
||||
decision. A target that is still broken keeps producing failures and stays.
|
||||
|
||||
The same rule applies on all three lanes and in both readers.
|
||||
`GET /api/queues/attention` (§9.3) and the `ntfy` notification (§9.5) are two
|
||||
views of one queue; filtering differently tells the operator two different
|
||||
stories on two channels.
|
||||
|
||||
## 6. Sourcing
|
||||
|
||||
### 6.1 Prowlarr, per-indexer Torznab
|
||||
@@ -400,6 +480,22 @@ its own, the escape hatch is the season release deck, not a lane exception.
|
||||
Targeted search backs off `1h → 6h → 1d → 3d`, capped at 7d, reset when the
|
||||
title's metadata changes. It never gives up entirely, it goes quiet.
|
||||
|
||||
**The ladder runs from the failure, not the grab.** A failed season-pack grab
|
||||
quiets the pack lane on that same curve, and the rung is measured from the
|
||||
moment the grab entered `failed` — `grabs.failed_at`, the column §5.7's
|
||||
attention window reads — not from when it was sent. The two are usually
|
||||
minutes apart, but a torrent can stall on a dead swarm for five weeks before
|
||||
`ffprobe` condemns it at import. Measured from the grab, the whole ladder has
|
||||
already elapsed by the time the failure lands, so the lane retries the source
|
||||
that just failed at once, which is the one thing the backoff exists to
|
||||
prevent. The ladder's job is to stay off a source that has recently failed,
|
||||
and "recently" can only mean recently failed.
|
||||
|
||||
One anchor covers both features. §5.7's window and this ladder ask the same
|
||||
question of the same event and read the same column; a target still broken
|
||||
keeps producing fresh failures, and each one both re-arms this backoff and
|
||||
holds the target in the attention queue.
|
||||
|
||||
**Do not search before the release exists.** TMDB carries release dates; a
|
||||
movie with no digital release date gets zero targeted searches. This is the
|
||||
single largest source of wasted queries in Radarr and it is free to avoid.
|
||||
@@ -468,6 +564,13 @@ Media kind first, hard audience boundary second, people nowhere.
|
||||
- **Release group is deliberately absent.** It is not a selection criterion and
|
||||
it makes filenames long enough to break a terminal.
|
||||
|
||||
Changing a title's root relocates its title folder into the new root; roots
|
||||
are assumed to share one filesystem, so the move is a rename, never a copy.
|
||||
Changing a root's path is the same move over every title under it, and it
|
||||
is all or nothing: one folder that cannot move puts back the ones that
|
||||
already did and leaves the root's path alone, so the stored path always
|
||||
describes the disk.
|
||||
|
||||
During transition, write into the existing roots so Jellyfin needs no
|
||||
reconfiguration and new content appears immediately. Radarr will not touch a
|
||||
folder it has no record of.
|
||||
@@ -576,7 +679,7 @@ notifying on everything and being muted within a week.
|
||||
|
||||
- **Imported** → to the title's owners. The only good-news notification.
|
||||
- **Needs a decision** → to the operator alone. Entered the no-PT-source queue,
|
||||
or hard-failed twice on different releases.
|
||||
or the needs-a-decision queue (§5.7).
|
||||
- **Broken** → to the operator alone. Prowlarr, Transmission or TMDB
|
||||
unreachable, disk full.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user