fix(401): reject Mirror channel with playout (422) + repair dead else-branch #456

Merged
timothy merged 2 commits from fix/401-updatechannel-mirror-422 into main 2026-07-18 18:27:26 +02:00
Owner

UpdateChannelHandler silently coerced a Mirror-mode request back to Generated when the channel had a playout — returning 200 with a value the client never asked for. Now the invalid transition (request PlayoutSource=Mirror AND channel already has a playout) is rejected with 422; Playouts is .Include()-loaded so the check is non-vacuous, and a GET→PUT round-trip of an unchanged Generated channel is unaffected.

Second commit repairs a data-integrity bug the independent review caught: ApplyUpdateRequest had a dead else-branch (Mirror-only fields were nulled for non-Mirror channels, then unconditionally overwritten two lines later), so removing the old coercion would let a stray MirrorSourceChannelId/PlayoutOffset persist onto a Generated channel. Fixed by scoping the assignment to the Mirror branch; negative-controlled (new test fails pre-fix, passes post-fix). 10/10 tests green.

Review: write-path — independent cold review found + I verified the dead-else regression; fix commit re-reviewed against the prescribed diff. Review-verdict posted below.

fixes #401

UpdateChannelHandler silently coerced a Mirror-mode request back to Generated when the channel had a playout — returning 200 with a value the client never asked for. Now the invalid transition (request PlayoutSource=Mirror AND channel already has a playout) is rejected with 422; `Playouts` is `.Include()`-loaded so the check is non-vacuous, and a GET→PUT round-trip of an unchanged Generated channel is unaffected. Second commit repairs a **data-integrity bug the independent review caught**: `ApplyUpdateRequest` had a dead else-branch (Mirror-only fields were nulled for non-Mirror channels, then unconditionally overwritten two lines later), so removing the old coercion would let a stray `MirrorSourceChannelId`/`PlayoutOffset` persist onto a Generated channel. Fixed by scoping the assignment to the Mirror branch; negative-controlled (new test fails pre-fix, passes post-fix). 10/10 tests green. **Review:** write-path — independent cold review found + I verified the dead-else regression; fix commit re-reviewed against the prescribed diff. Review-verdict posted below. fixes #401
timothy added 2 commits 2026-07-18 16:59:23 +02:00
fixes #401

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(401): null Mirror-only fields for non-Mirror channels (repair dead else-branch)
Build ErsatzTV Image / CI image pin matches docker/ci (pull_request) Successful in 5s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 6s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 7s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 15s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 2m53s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 5m53s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 14m34s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 19m47s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
10d891ba63
Closes review finding: stray MirrorSourceChannelId/PlayoutOffset could persist onto a Generated channel.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author
Owner

Review-verdict: MERGEABLE @ 10d891ba63
Write-path. Independent cold review found a real data-integrity regression (dead else-branch → stray MirrorSourceChannelId/PlayoutOffset could persist on a Generated channel); fixed in commit 10d891ba63 and negative-controlled (new test fails pre-fix, passes post-fix). Fix commit re-reviewed against the prescribed diff. 10/10 tests green.

Review-verdict: MERGEABLE @ 10d891ba63572add9c9f7aa7daeb69bc872a8d51 Write-path. Independent cold review found a real data-integrity regression (dead else-branch → stray MirrorSourceChannelId/PlayoutOffset could persist on a Generated channel); fixed in commit 10d891ba63572add9c9f7aa7daeb69bc872a8d51 and negative-controlled (new test fails pre-fix, passes post-fix). Fix commit re-reviewed against the prescribed diff. 10/10 tests green.
Author
Owner

Live-E2E PASSED (scripts/e2e-local.sh, real /api endpoints, machine-key auth). Every mutation round-trips through a subsequent GET — the write-path requirement per docs/e2e-local.md is satisfied. Proceeding to merge.

Live-E2E PASSED (scripts/e2e-local.sh, real /api endpoints, machine-key auth). Every mutation round-trips through a subsequent GET — the write-path requirement per docs/e2e-local.md is satisfied. Proceeding to merge.
timothy merged commit ca87336600 into main 2026-07-18 18:27:26 +02:00
Sign in to join this conversation.