Per-source rotation weights (weighted round-robin, e.g. 3x Show A / 1x Show B) + query corrections (exclude / add-untagged) for auto-tune channels, supplied at bulk-create time via an optional sources: [{sourceId, weight, excluded}] on POST /api/v1/channels/auto-tune.
Design (Option A, reuse #70): a customized channel is backed by a system-owned MultiCollection of per-source SmartCollections with PlaybackOrder.WeightedShuffle — the path WeightedShuffleCollectionEnumerator already consumes. All-default weights keep the #69 single-SmartCollection shape. Full rationale in docs/decisions.md 2026-07-18 (#425).
Discriminators: TV -> live show_title:"X"; movies -> stable id:{mediaItemId}.
Materialization axis-dependent: TV materializes every base show individually + a live remainder (weight 1); MovieGenre materializes only touched movies + one count-weighted remainder.
Remainder = (base) AND NOT (materialized ∪ excluded) — a partition.
Ownership: new nullable OwnedByChannelId on SmartCollection + MultiCollection (dual-provider migration); owned rows hidden from all collection lists + the scheduling pickers, cascade-cleaned on channel delete.
Verification:
2268+ backend tests green; new AutoTuneAxisMap partition units, DB-backed weighted-path handler tests, delete-cleanup, and picker-hiding regression tests.
Live-E2E: created a weighted channel (Alpha=3, Beta=1) via the real API -> built playout (183k items, no errors) aired Alpha:Beta = 3.00 exactly, confirmed in both the playout and the guide programmes (75%/25%); M3U + XMLTV valid.
Independent adversarial review (cold Opus agent) found + fixed a blocker (search pickers leaked owned artifacts -> user-schedule-delete via FK cascade) plus two mediums (ownership-stamp cancellation/atomicity; documented the >1000-source weight-clamp bound).
Fixes #425.
Per-source rotation weights (weighted round-robin, e.g. 3x Show A / 1x Show B) + query corrections (exclude / add-untagged) for auto-tune channels, supplied at bulk-create time via an optional `sources: [{sourceId, weight, excluded}]` on `POST /api/v1/channels/auto-tune`.
**Design (Option A, reuse #70):** a customized channel is backed by a system-owned `MultiCollection` of per-source `SmartCollection`s with `PlaybackOrder.WeightedShuffle` — the path `WeightedShuffleCollectionEnumerator` already consumes. All-default weights keep the #69 single-SmartCollection shape. Full rationale in `docs/decisions.md` 2026-07-18 (#425).
- **Discriminators**: TV -> live `show_title:"X"`; movies -> stable `id:{mediaItemId}`.
- **Materialization axis-dependent**: TV materializes every base show individually + a live remainder (weight 1); MovieGenre materializes only touched movies + one count-weighted remainder.
- **Remainder** = `(base) AND NOT (materialized ∪ excluded)` — a partition.
- **Ownership**: new nullable `OwnedByChannelId` on SmartCollection + MultiCollection (dual-provider migration); owned rows hidden from all collection lists + the scheduling pickers, cascade-cleaned on channel delete.
**Verification:**
- 2268+ backend tests green; new AutoTuneAxisMap partition units, DB-backed weighted-path handler tests, delete-cleanup, and picker-hiding regression tests.
- **Live-E2E**: created a weighted channel (Alpha=3, Beta=1) via the real API -> built playout (183k items, no errors) aired Alpha:Beta = **3.00** exactly, confirmed in both the playout and the guide programmes (75%/25%); M3U + XMLTV valid.
- **Independent adversarial review** (cold Opus agent) found + fixed a blocker (search pickers leaked owned artifacts -> user-schedule-delete via FK cascade) plus two mediums (ownership-stamp cancellation/atomicity; documented the >1000-source weight-clamp bound).
Docs updated: decisions.md, domain-model.md, api-conventions.md; OpenAPI trio regenerated.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Initial PR diff — found a BLOCKER (SearchSmart/MultiCollections pickers leaked owned artifacts → user-schedule-delete via FK cascade) + 2 mediums (ownership-stamp cancellation/atomicity; undocumented >1000 weight-clamp bound). All fixed in 27221572.
Re-review of the fix commit (27221572) — confirmed all four fixes correct, the picker regression test non-vacuous, and no other live leaking surface (GetById handlers are direct lookups, not pickers; QuerySearchTargetsHandler is orphaned/never-dispatched Blazor-era dead code — noted for whoever revives it, not a live leak). Verdict: MERGEABLE.
Live-E2E: weighted channel (Alpha=3, Beta=1) → built playout aired Alpha:Beta = 3.00 exactly, confirmed in playout + guide. All 6 ## Done-when boxes ticked. Awaiting CI green before merge.
Review-verdict: MERGEABLE @ 27221572158114f5a43a40c94c743322c3e49d76
Two independent cold-agent (Opus) review passes:
1. **Initial PR diff** — found a BLOCKER (SearchSmart/MultiCollections pickers leaked owned artifacts → user-schedule-delete via FK cascade) + 2 mediums (ownership-stamp cancellation/atomicity; undocumented >1000 weight-clamp bound). All fixed in 27221572.
2. **Re-review of the fix commit** (27221572) — confirmed all four fixes correct, the picker regression test non-vacuous, and no other *live* leaking surface (`GetById` handlers are direct lookups, not pickers; `QuerySearchTargetsHandler` is orphaned/never-dispatched Blazor-era dead code — noted for whoever revives it, not a live leak). Verdict: MERGEABLE.
Live-E2E: weighted channel (Alpha=3, Beta=1) → built playout aired Alpha:Beta = 3.00 exactly, confirmed in playout + guide. All 6 `## Done-when` boxes ticked. Awaiting CI green before merge.
Rebased onto origin/main (was 26 commits behind). The rebase changed no reviewed code — only: resolved the append-only docs/decisions.md conflict (kept both the #386/#176 entries and this PR's #425 entry), and regenerated the OpenAPI trio (check:api clean, no drift). My migration is still the latest (no competing migration landed; snapshot has no drift). Full solution rebuilds clean and the auto-tune/picker/delete tests pass on the new base. Prior two-pass review (blocker + fix-commit re-review) verdicts stand at this head.
Review-verdict: MERGEABLE @ 3bca000e27dc5bfd6241f681658db8e3067986c2
Rebased onto origin/main (was 26 commits behind). The rebase changed **no reviewed code** — only: resolved the append-only `docs/decisions.md` conflict (kept both the #386/#176 entries and this PR's #425 entry), and regenerated the OpenAPI trio (`check:api` clean, no drift). My migration is still the latest (no competing migration landed; snapshot has no drift). Full solution rebuilds clean and the auto-tune/picker/delete tests pass on the new base. Prior two-pass review (blocker + fix-commit re-review) verdicts stand at this head.
timothy
merged commit 3a463db36a into main2026-07-18 04:29:17 +02:00
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.
Fixes #425.
Per-source rotation weights (weighted round-robin, e.g. 3x Show A / 1x Show B) + query corrections (exclude / add-untagged) for auto-tune channels, supplied at bulk-create time via an optional
sources: [{sourceId, weight, excluded}]onPOST /api/v1/channels/auto-tune.Design (Option A, reuse #70): a customized channel is backed by a system-owned
MultiCollectionof per-sourceSmartCollections withPlaybackOrder.WeightedShuffle— the pathWeightedShuffleCollectionEnumeratoralready consumes. All-default weights keep the #69 single-SmartCollection shape. Full rationale indocs/decisions.md2026-07-18 (#425).show_title:"X"; movies -> stableid:{mediaItemId}.(base) AND NOT (materialized ∪ excluded)— a partition.OwnedByChannelIdon SmartCollection + MultiCollection (dual-provider migration); owned rows hidden from all collection lists + the scheduling pickers, cascade-cleaned on channel delete.Verification:
Docs updated: decisions.md, domain-model.md, api-conventions.md; OpenAPI trio regenerated.
🤖 Generated with Claude Code
Review-verdict: MERGEABLE @ 27221572158114f5a43a40c94c743322c3e49d76
Two independent cold-agent (Opus) review passes:
GetByIdhandlers are direct lookups, not pickers;QuerySearchTargetsHandleris orphaned/never-dispatched Blazor-era dead code — noted for whoever revives it, not a live leak). Verdict: MERGEABLE.Live-E2E: weighted channel (Alpha=3, Beta=1) → built playout aired Alpha:Beta = 3.00 exactly, confirmed in playout + guide. All 6
## Done-whenboxes ticked. Awaiting CI green before merge.2722157215to3bca000e27Review-verdict: MERGEABLE @
3bca000e27Rebased onto origin/main (was 26 commits behind). The rebase changed no reviewed code — only: resolved the append-only
docs/decisions.mdconflict (kept both the #386/#176 entries and this PR's #425 entry), and regenerated the OpenAPI trio (check:apiclean, no drift). My migration is still the latest (no competing migration landed; snapshot has no drift). Full solution rebuilds clean and the auto-tune/picker/delete tests pass on the new base. Prior two-pass review (blocker + fix-commit re-review) verdicts stand at this head.