Policy filter and verdict pipeline #54
Merged
naps62-yolo
merged 4 commits from 2026-08-22 20:18:28 +01:00
issue/10-policy-engine into main
Labels
Clear labels
area/api
area/ci
area/compat
area/core
area/daemon
area/db
area/dl
area/indexer
area/infra
area/meta
area/parse
area/probe
area/subs
area/web
difficulty/easy
difficulty/hard
difficulty/moderate
difficulty/trivial
phase/1-skeleton
phase/2-logic
phase/3-sourcing
phase/4-movies
phase/5-ui
phase/6-tv
phase/7-people
phase/8-compat
phase/9-subtitles
type/bug
type/chore
type/feature
type/test
arr-api
CI and test harness
arr-compat
arr-core
arr-daemon
arr-db
arr-dl
arr-indexer
workspace and tooling
arr-meta
arr-parse
arr-probe
arr-subs crate: providers, extraction, translation, sync
web/
bounded, obvious approach, few files
subtle correctness or cross-cutting
multiple files, judgement, an interface to design
one file, mechanical, no design decisions
workspace, CI, config, database, empty API and SPA
parsing and policy engine, pure, no network
TMDB and Prowlarr, read-only
movies end to end
search, buckets, library views, queues
seasons, episodes, tracking, derived status
owner tags and notifications
Jellyseerr shim
build order: subtitles, DESIGN.md §15
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: yolo/arr#54
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Adds the pure pre-grab/post-download policy pipeline in
arr-core.Rules preserve unknown evidence, classify failures inside the engine, and reduce hard failures before soft waivers. Resolution filtering,
only_4k, and the CAM/telesync/screener source filters are included. Parsed releases usearr-parseclaims directly; language, HDR, and scoring remain separate rule extensions.Closes #10
Verification
just ci(50 tests)arr-coredependency tree contains no axum, sqlx, or reqwestReviewed
ba180ec. Two behaviour findings on the pipeline, two smaller ones inline.Two remarks with no line to sit on:
Rule::ResolutionUnknownandRule::SourceUnknownare added toRule, which is the typeVerdictandMediaFile.waivername. Neither variant can ever reach either, since Unknown never decides a verdict. They only exist insideEvaluation.rules. A separate type for report entries would keepRulemeaning "a rule that decided something".pub use arr_parse::NameClaims as ParsedReleaseputsarr_parsetypes intoarr-core's public surface, soRelease.parsednow storesarr_parse::Resolution/Sourceand every consumer converts at each use, asCandidatedoes per call. Works, and it removes the duplicate struct; worth being deliberate about since it is the stored shape.@@ -112,5 +112,5 @@pub size_bands: BTreeMap<Resolution, SizeBand>,pub resolution_preference: Vec<Resolution>,pub source_weights: BTreeMap<Source, i32>,}HdrClaimhas no users left in the workspace afterParsedReleasebecame an alias forNameClaims, which carriesarr_parse::HdrMarkerinstead. Drop it, or leave it for the HDR rule issue knowing it now duplicatesHdrMarker.@@ -0,0 +104,4 @@/// Reduce an ordered set of policy rules to a verdict.////// The first hard failure decides rejection. If none exists, the first soft/// failure decides the waiver. Unknown evidence never becomes a pass.The doc says "Unknown evidence never becomes a pass", but a candidate where every rule is Unknown reduces to
Verdict::Eligible, which is the pass.missing_pre_grab_evidence_stays_unknownasserts exactly that.Pre-grab that is the right call. Post-download it is not: a
ProbedMediawithsource: NoneorSource::Othercomes backEligiblewith no waiver, so a caller onevaluate()cannot tell "passed every rule" from "had no evidence for any of them". Issue #10 asks for unknown rather than pass, and onlyevaluate_with_reportpreserves it.Either make post-download Unknown produce a waiver, or reword the doc to say Unknown abstains and the verdict alone does not carry it.
@@ -0,0 +144,4 @@&& (!context.overrides.only_4k || resolution == Resolution::R2160p);let outcome = if accepted {RuleOutcome::Pass} else if context.candidate.phase() == EvaluationPhase::PostDownload {A disallowed resolution post-download is SoftFail unconditionally, so every case imports with a waiver.
DESIGN §5.7 names one soft case: 1080p while
only_4kwas set. §5.6's whole premise is that names lie — grab a release claiming 2160p, ffprobe reports 480p, and this waives it and imports the file. That is a hard fail under §5.7 ("useless... blacklist the release, grab the next candidate"), not a waiver.Suggest splitting: soft when the resolution is in
policy.resolution_preferenceand onlyonly_4kexcluded it, hard when it is outside the root policy entirely.@@ -0,0 +422,4 @@Candidate::PostDownload(&media),);assert_eq!(report.rules[1].outcome, RuleOutcome::Unknown);The name says "not treated as a pass", but the assertion only checks
rules[1].outcome. The verdict here isVerdict::Eligibleand the test passes anyway. Assert the verdict too, so whatever is decided for the first finding is pinned by this test.Addressed in
3a303e2. Post-download resolutions outside the root policy now hard-fail; only anonly_4koverride mismatch is soft.evaluatenow returns the full report so unknown evidence cannot be discarded accidentally, and report-only rule identities no longer inhabitRule. The unusedHdrClaimtype is removed.Release.parseddeliberately remains the completearr_parse::NameClaimsshape. Converting it to the old core struct would discard title, year, codec, edition, group, and marker distinctions; conversion to probed domain values stays at rule evaluation.Verification
just ci— 50 tests passed.Reviewed
3a303e2. All four earlier findings are addressed: resolution now hard-fails post-download unlessonly_4kis the sole reason,RuleEvaluationcarries the concreteRuleonly where one exists,evaluatereturns the report so the verdict can no longer hide an Unknown, andHdrClaimis gone.One leftover inline.
@@ -0,0 +53,4 @@/// A rule's result before the pipeline reduces all results to one verdict.#[derive(Clone, Copy, Debug, Eq, PartialEq)]pub enum RuleOutcome {RuleOutcomeandRuleEvaluation::outcome()have no callers left after the refactor — reduction now matches onRuleEvaluationdirectly and the tests compare variants. Same shape as theHdrClaimleftover: drop both, or keep them knowing they are public API with no user yet.Reviewed
4e1876d. No findings.