Skip to content

feat(btn): implement evidence-backed full handling - #331

Open
Audionut wants to merge 10 commits into
refactor/evidence-driven-dupe-checkingfrom
feat/btn-full-handling
Open

feat(btn): implement evidence-backed full handling#331
Audionut wants to merge 10 commits into
refactor/evidence-driven-dupe-checkingfrom
feat/btn-full-handling

Conversation

@Audionut

@Audionut Audionut commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Dependency

Stacked PR: This branch was created from and targets refactor/evidence-driven-dupe-checking. It depends on #327 and contains only the BTN follow-up changes relative to that base. Review and merge #327 first; then retarget or rebase this PR as needed and verify its effective diff is unchanged.

Summary

  • Reconciles coarse SD with concrete 480/576 evidence at the shared dupe normalization root.
  • Retains typed BTN category, source, codec, container, origin, group, provider IDs, size, flags, timestamps, totals, and pagination evidence.
  • Adds evidence-backed BTN coexistence, precedence, pack-capacity, validation, naming, reservation, and stale-claim-cache behavior.
  • Treats ordinary searches as complete only when stable reported totals equal unique returned IDs; daily searches remain one-shot and explicitly incomplete on count mismatch.

Scope

General movies remain unsupported. Manual TV-movie/special questionnaires, separate folder-name output, and internal/language/pilot rules lacking authoritative bilateral facts remain deferred; mixed packs and staff-owned decisions stay reviewable.

Validation

  • make lint
  • make test-go
  • make test-frontend
  • make backend
  • focused BTN/shared race tests and WebUI/API/CLI race tests
  • make gofix-check-changed, make logpolicy, contract generation check, and git diff --check

Links

Summary by CodeRabbit

  • New Features

    • Added more precise duplicate detection using release origin, resolution, provider data, and tracker-specific slot rules.
    • Added BTN release-name normalization and expanded metadata handling.
    • Added stricter validation for media, packaging, season packs, and unsupported formats.
    • Improved season-pack reservation checks with complete, paginated search results and timestamp validation.
    • Release-origin details are now available in duplicate evaluations and workflow data.
  • Bug Fixes

    • Exact duplicates now consistently block uploads and provide clearer status messages.
    • Coexisting releases no longer appear as duplicate matches.
    • Risk acknowledgement is requested only when the assessment requires review.

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds BTN duplicate detection, typed metadata mapping, release-origin and resolution comparisons, upload naming and validation rules, paginated reservation checks, workflow output updates, and stale-cache fallback coverage.

Changes

BTN duplicate evaluation

Layer / File(s) Summary
Shared duplicate contracts and fact comparison
internal/trackers/definition.go, internal/trackers/dupe/*, pkg/api/*, internal/trackers/projection.go
Duplicate data now carries release origins and provider IDs. Resolution overlap and configured dimensions use specialized comparisons.
Duplicate evaluation and public outcomes
internal/trackers/dupe/*, internal/core/workflow_dupes.go, webui/src/pages/dupe_check/*
Evaluations now apply relation ordering and blocking precedence. Coexistence results are excluded from public duplicate matches, and risk acknowledgement depends on pending decisions.
BTN metadata mapping and duplicate policy
internal/trackers/impl/standalone/btn/{dupe.go,dupe_policy.go,profile.go,definition.go}
BTN responses use typed metadata records. The profile adds origin, codec, coexistence, manual-review, and capacity rules.
BTN naming and validation
internal/trackers/impl/standalone/btn/{name.go,validation.go}
Upload names normalize BTN-specific release patterns. Validation checks package structure, media metadata, TV packs, mixed groups, and DVD formats.
BTN reservation evidence and cache fallback
internal/trackers/impl/standalone/btn/upload.go, internal/metadata/tracker_data_test.go
Reservation checks paginate getTorrents, validate timestamps and response completeness, and apply a two-hour window. Stale claimed-title cache data is retained after fetch failure.

Estimated code review effort: 5 (Critical) | ~90 minutes

Possibly related PRs

  • autobrr/upbrr#160: Both changes modify BTN upload-title normalization and metadata handling.
  • autobrr/upbrr#215: Both changes modify BTN duplicate and release-origin handling.
  • autobrr/upbrr#316: Both changes modify shared duplicate detection and tracker policy behavior.

Poem

A rabbit checks each release line,
With origins clear and slots aligned.
BTN names shed stray marks,
While paged checks guard upload parks.
Stale cache waits when fetches fail—
Hop by hop, the rules prevail.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.28% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the BTN feature and the main evidence-backed handling implemented by the changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/btn-full-handling

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Audionut

Audionut commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🧹 Nitpick comments (1)
internal/trackers/impl/standalone/btn/upload.go (1)

1310-1346: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Log evidence-unavailable decisions.

The canonical-season, API-error, missing-release-name, and invalid-timestamp branches block the upload without a warning log. Emit a Warnf with a stable decision=evidence_unavailable field before these returns. Do not include the remote error or response content.

As per coding guidelines, “use warnings for blocked outcomes.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/trackers/impl/standalone/btn/upload.go` around lines 1310 - 1346,
Add a Warnf with stable decision=evidence_unavailable context immediately before
each evidence-unavailable return in the BTN reservation check: missing canonical
season, btnAPISearchTorrents failure, missing release name, and invalid matching
timestamp. Keep remote error and response content out of these warnings, while
preserving the existing error returns and blocked/allowed logs.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/trackerdata/BTN_FULL_HANDLING_IMPLEMENTATION_PLAN.md`:
- Around line 62-72: Label the “Current-State Findings” section in the
implementation plan as pre-implementation behavior, such as by renaming it to
“Pre-implementation Findings.” Ensure the statements about the 50-result cap,
empty DupeEntry.Source, and missing BTN DupePolicy are clearly presented as
historical findings rather than the current contract.
- Around line 37-39: Clarify the daily-show search rules in the implementation
plan: specify whether a daily response whose returned row count matches BTN’s
reported results count qualifies as an authoritative date-and-category scope for
public complete=true. If it does, document that explicit exception and its
conditions; otherwise require all daily responses to remain incomplete.

In `@docs/trackerdata/EVIDENCE.md`:
- Around line 8-11: Update the completion contract in EVIDENCE.md so daily
searches explicitly either qualify as an exception to provider/tracker-group
binding or never return complete=true; align the later BTN daily-search guidance
and implementation plan with that same rule, avoiding wording that treats a
count-matching daily response as complete by default.
- Around line 142-143: Align the Phase 3 disposition for internal, language, and
pilot coexistence across docs/trackerdata/EVIDENCE.md (lines 142-143) and
docs/trackerdata/BTN_FULL_HANDLING_IMPLEMENTATION_PLAN.md (lines 210-225):
either remove these cases from the automatic plan or document the exact typed
facts, evidence ID, policy, and tests that justify automation. Ensure both
documents consistently reflect the same disposition.

In `@internal/metadata/tracker_data_test.go`:
- Around line 542-553: Update the test around loadBTNClaimedTitles to make the
initial request succeed and the subsequent claimed-titles refresh request fail
with context.Canceled. Record transport calls, assert the expected request count
before validating the stale cached title fallback, and keep the existing
fallback assertion.

In `@internal/trackers/dupe/set.go`:
- Line 190: Update the predicate comparison branch using compareDimensionFacts
so DimensionUnknown is added to missing alongside other non-matching results,
while preserving the existing DimensionEqual handling. Ensure ambiguous SD
versus concrete SD resolution is treated as indeterminate and the capacity rule
fails closed.

In `@internal/trackers/impl/standalone/btn/definition.go`:
- Around line 51-52: Update the normalization in isBTNInternalGroupName to trim
surrounding whitespace before calling strings.TrimPrefix, then retain the
existing lowercase normalization and internal-group matching behavior.

In `@internal/trackers/impl/standalone/btn/dupe_policy.go`:
- Around line 45-60: Require authoritative bilateral facts by setting
RequiresComplete: true on the generic different-value condition, the WEB source
condition in btnWEBCodecRule, and the related codec condition. Preserve
ambiguous or partial-fact cases as deferred for review rather than allowing
evaluateTrackerRule to make an automatic decision.

In `@internal/trackers/impl/standalone/btn/dupe.go`:
- Around line 503-509: Update firstPresent to skip entries whose value is nil,
continuing through subsequent aliases instead of returning them as present; only
return true for non-nil values, preserving the existing false result when no
usable alias exists. Add or update a btnFlags test covering null HDR fields to
verify flagsPresent and flagsComplete remain false when no non-null HDR evidence
is available.

In `@internal/trackers/impl/standalone/btn/upload_integration_test.go`:
- Around line 1898-1906: The httptest server handler must validate pagination
offsets instead of selecting responses solely by call count. Decode each
request’s JSON-RPC parameters, assert offset zero on the first request and the
expected next-page offset on the second, then retain the existing page-specific
responses and unexpected-request failure behavior.

In `@internal/trackers/impl/standalone/btn/upload.go`:
- Around line 1386-1390: Update the page-torrent aggregation loop to reject
duplicate IDs before assigning to torrents: after validating the trimmed ID in
the existing search flow, check whether id already exists in torrents and return
a malformed-evidence error if so; only insert unseen IDs.

---

Nitpick comments:
In `@internal/trackers/impl/standalone/btn/upload.go`:
- Around line 1310-1346: Add a Warnf with stable decision=evidence_unavailable
context immediately before each evidence-unavailable return in the BTN
reservation check: missing canonical season, btnAPISearchTorrents failure,
missing release name, and invalid matching timestamp. Keep remote error and
response content out of these warnings, while preserving the existing error
returns and blocked/allowed logs.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 53594069-0074-41fb-a706-58f41aae392b

📥 Commits

Reviewing files that changed from the base of the PR and between 4394369 and 84ae31d.

⛔ Files ignored due to path filters (1)
  • webui/src/api/generated/release-workflow.ts is excluded by !**/generated/**
📒 Files selected for processing (30)
  • docs/trackerdata/BTN_FULL_HANDLING_IMPLEMENTATION_PLAN.md
  • docs/trackerdata/EVIDENCE.md
  • internal/metadata/tracker_data_test.go
  • internal/trackers/definition.go
  • internal/trackers/dupe/candidate.go
  • internal/trackers/dupe/evaluator.go
  • internal/trackers/dupe/findings.go
  • internal/trackers/dupe/normalize.go
  • internal/trackers/dupe/normalize_test.go
  • internal/trackers/dupe/service_test.go
  • internal/trackers/dupe/set.go
  • internal/trackers/dupe/types.go
  • internal/trackers/impl/responsibility_ledger_test.go
  • internal/trackers/impl/standalone/btn/definition.go
  • internal/trackers/impl/standalone/btn/definition_test.go
  • internal/trackers/impl/standalone/btn/dupe.go
  • internal/trackers/impl/standalone/btn/dupe_policy.go
  • internal/trackers/impl/standalone/btn/dupe_policy_test.go
  • internal/trackers/impl/standalone/btn/dupe_test.go
  • internal/trackers/impl/standalone/btn/name.go
  • internal/trackers/impl/standalone/btn/name_test.go
  • internal/trackers/impl/standalone/btn/profile.go
  • internal/trackers/impl/standalone/btn/upload.go
  • internal/trackers/impl/standalone/btn/upload_integration_test.go
  • internal/trackers/impl/standalone/btn/validation.go
  • internal/trackers/impl/standalone/btn/validation_test.go
  • internal/trackers/projection.go
  • internal/webserver/openapi/release-workflow-v1.json
  • pkg/api/dupes.go
  • pkg/api/workflow_contracts.go

Comment thread docs/trackerdata/BTN_FULL_HANDLING_IMPLEMENTATION_PLAN.md Outdated
Comment thread docs/trackerdata/BTN_FULL_HANDLING_IMPLEMENTATION_PLAN.md Outdated
Comment thread docs/trackerdata/EVIDENCE.md Outdated
Comment thread docs/trackerdata/EVIDENCE.md Outdated
Comment thread internal/metadata/tracker_data_test.go Outdated
Comment thread internal/trackers/impl/standalone/btn/definition.go Outdated
Comment thread internal/trackers/impl/standalone/btn/dupe_policy.go
Comment thread internal/trackers/impl/standalone/btn/dupe.go
Comment thread internal/trackers/impl/standalone/btn/upload_integration_test.go Outdated
Comment thread internal/trackers/impl/standalone/btn/upload.go
@Audionut

Audionut commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

Fixed the embedded reservation logging nitpick in 5aca6f60: evidence-unavailable paths now log safe reason-only decisions without remote errors or payloads.

@Audionut

Audionut commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/core/workflow_dupes.go (1)

271-300: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Project ReleaseOrigin in public workflow matches.

publicDupeMatches copies candidate facts but omits evaluation.ReleaseOrigin. This drops BTN origin evidence from the workflow API. Add ReleaseOrigin to api.DupeMatchProjection here and add regression coverage.

As per coding guidelines, preserve CLI and WebUI behavior where workflows are shared.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/core/workflow_dupes.go` around lines 271 - 300, Update the
api.DupeMatchProjection construction in publicDupeMatches to copy
evaluation.ReleaseOrigin, preserving the candidate’s origin evidence in public
workflow matches. Add regression coverage verifying ReleaseOrigin is included,
without changing shared CLI or WebUI behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/trackers/impl/standalone/btn/dupe_policy_test.go`:
- Around line 47-60: Correct the “web without region ignores pal ntsc” test
fixtures so both target and candidate use the same episode, and add explicit
opposing PAL/NTSC evidence to exercise the Web-region rule. Keep the expected
DupeRelationCoexists result and the existing btnPolicyTarget/btnPolicyCandidate
setup.

---

Outside diff comments:
In `@internal/core/workflow_dupes.go`:
- Around line 271-300: Update the api.DupeMatchProjection construction in
publicDupeMatches to copy evaluation.ReleaseOrigin, preserving the candidate’s
origin evidence in public workflow matches. Add regression coverage verifying
ReleaseOrigin is included, without changing shared CLI or WebUI behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 476fd375-27ed-4834-920c-cf1eb2a7978f

📥 Commits

Reviewing files that changed from the base of the PR and between 84ae31d and d777739.

📒 Files selected for processing (24)
  • internal/core/workflow_dupes.go
  • internal/core/workflow_dupes_test.go
  • internal/core/workflow_preflight.go
  • internal/metadata/tracker_data_test.go
  • internal/trackers/dupe/evaluator.go
  • internal/trackers/dupe/evaluator_test.go
  • internal/trackers/dupe/findings.go
  • internal/trackers/dupe/service.go
  • internal/trackers/dupe/service_test.go
  • internal/trackers/dupe/set.go
  • internal/trackers/dupe/set_test.go
  • internal/trackers/impl/standalone/btn/definition.go
  • internal/trackers/impl/standalone/btn/dupe.go
  • internal/trackers/impl/standalone/btn/dupe_policy.go
  • internal/trackers/impl/standalone/btn/dupe_policy_test.go
  • internal/trackers/impl/standalone/btn/dupe_test.go
  • internal/trackers/impl/standalone/btn/upload.go
  • internal/trackers/impl/standalone/btn/upload_integration_test.go
  • internal/trackers/impl/standalone/btn/validation.go
  • internal/trackers/impl/standalone/btn/validation_test.go
  • internal/trackers/projection.go
  • internal/trackers/projection_test.go
  • webui/src/pages/dupe_check/index.test.tsx
  • webui/src/pages/dupe_check/index.tsx
🚧 Files skipped from review as they are similar to previous changes (11)
  • internal/trackers/impl/standalone/btn/validation.go
  • internal/trackers/dupe/set.go
  • internal/trackers/dupe/findings.go
  • internal/metadata/tracker_data_test.go
  • internal/trackers/impl/standalone/btn/validation_test.go
  • internal/trackers/impl/standalone/btn/definition.go
  • internal/trackers/impl/standalone/btn/dupe_policy.go
  • internal/trackers/impl/standalone/btn/upload.go
  • internal/trackers/projection.go
  • internal/trackers/impl/standalone/btn/dupe_test.go
  • internal/trackers/impl/standalone/btn/upload_integration_test.go

Comment on lines +47 to +60
{
name: "web without region ignores pal ntsc",
target: func() api.TrackerDuplicateTarget {
target := btnPolicyTarget("WEB-DL", "1080p", "H.264", "P2P")
target.Episode = 12
return target
}(),
candidate: func() dupe.TrackerCandidate {
candidate := btnPolicyCandidate("WEB-DL", "1080p", "H.264", "P2P")
candidate.Episode = 13
return candidate
}(),
want: api.DupeRelationCoexists,
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Test the stated PAL/NTSC behavior.

The fixtures use different episodes and add no PAL or NTSC evidence. Coexists can result from disjoint episodes, so this test does not protect the Web-region rule. Use the same episode and explicit PAL/NTSC evidence.

Proposed test correction
 target := btnPolicyTarget("WEB-DL", "1080p", "H.264", "P2P")
 target.Episode = 12
+target.Names = []string{"Example.Show.S01E12.PAL.WEB-DL-GRP"}

 candidate := btnPolicyCandidate("WEB-DL", "1080p", "H.264", "P2P")
-candidate.Episode = 13
+candidate.Episode = 12
+candidate.Name = "Example.Show.S01E12.NTSC.WEB-DL-GRP"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{
name: "web without region ignores pal ntsc",
target: func() api.TrackerDuplicateTarget {
target := btnPolicyTarget("WEB-DL", "1080p", "H.264", "P2P")
target.Episode = 12
return target
}(),
candidate: func() dupe.TrackerCandidate {
candidate := btnPolicyCandidate("WEB-DL", "1080p", "H.264", "P2P")
candidate.Episode = 13
return candidate
}(),
want: api.DupeRelationCoexists,
},
{
name: "web without region ignores pal ntsc",
target: func() api.TrackerDuplicateTarget {
target := btnPolicyTarget("WEB-DL", "1080p", "H.264", "P2P")
target.Episode = 12
target.Names = []string{"Example.Show.S01E12.PAL.WEB-DL-GRP"}
return target
}(),
candidate: func() dupe.TrackerCandidate {
candidate := btnPolicyCandidate("WEB-DL", "1080p", "H.264", "P2P")
candidate.Episode = 12
candidate.Name = "Example.Show.S01E12.NTSC.WEB-DL-GRP"
return candidate
}(),
want: api.DupeRelationCoexists,
},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/trackers/impl/standalone/btn/dupe_policy_test.go` around lines 47 -
60, Correct the “web without region ignores pal ntsc” test fixtures so both
target and candidate use the same episode, and add explicit opposing PAL/NTSC
evidence to exercise the Web-region rule. Keep the expected DupeRelationCoexists
result and the existing btnPolicyTarget/btnPolicyCandidate setup.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant