docs(779): ReplacePlayoutAlternateScheduleItemsHandler is over ProgramScheduleAlternate, not PlayoutTemplate
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 12s
Build ErsatzTV Image / Delimiter ban (release path) (pull_request) Successful in 18s
PR Gates / Docs update reminder (pull_request) Successful in 16s
PR Gates / decisions lifecycle (pull_request) Successful in 23s
PR Gates / Fix proofs (Proves trailers) (pull_request) Successful in 12s
Review verdict / Set review-verdict status (pull_request_target) Successful in 19s
PR Gates / Script tests (pytest) (pull_request) Successful in 4m26s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m48s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 6m17s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Skipped
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 6m11s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 9s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 7s
review-verdict/h10 Review-verdict: MERGEABLE @ 8387e16 (base: main)

Round-3 review found a factual error introduced by round 3 itself, in the commit
whose stated purpose was to stop shipping unverified assertions. Verified:
ReplacePlayoutAlternateScheduleItemsHandler mutates playout.ProgramScheduleAlternates
and constructs ProgramScheduleAlternate — a different entity from PlayoutTemplate.
They share only the IAlternateScheduleItem interface, which is where the
conflation came from.

The same sentence also attributed those handlers' id-based reconcile to
protecting progression state. That is unsupported: PlayoutTemplateId and
ProgramScheduleAlternateId appear NOWHERE in the solution, model snapshot
included, so no table holds an FK to either row's id. Only
ReplaceProgramScheduleItemsHandler is demonstrably protecting identity-keyed
state (PlayoutScheduleItemFillGroupIndex -> ProgramScheduleItemId). The record
now says which of the three that applies to and declines to explain the others.

Also drops a definite article that had survived the previous softening: "The
mutation class that would redden True/TRUE" was still an unexecuted uniqueness
claim in a record whose thesis forbids exactly those. Now "A mutation class".

Independent reproduction of the two mutation results in that record, by the
reviewer, in its own worktree: all-null -> GREEN; index-0-only -> RED with
"Id differs, should be 4 but was 7". The new id confirms the row was inserted
as new rather than updated.

Refs #779

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-21 20:59:19 +02:00
co-authored by Claude Opus 5
parent d0b2abb7ca
commit 8387e16d98
2 changed files with 12 additions and 6 deletions
@@ -55,7 +55,7 @@ the headline.** Flipping the shipped default `true` -> `false` reddens
`Read_Gating_Is_Required_When_The_Setting_Is_Absent` and only that test. That establishes the
*absent* cell is load-bearing. It also shows the three explicitly-configured `true`/`True`/`TRUE`
cases — the cells this rule is named for — are NOT independently load-bearing against that mutation:
a mistyped configuration key is caught, but by the `false` cases, not the `true` ones. The mutation class that would
a mistyped configuration key is caught, but by the `false` cases, not the `true` ones. A mutation class that would
redden `"True"`/`"TRUE"` alone is a hand-rolled case-sensitive parse replacing `GetValue`. They are kept as behaviour coverage of the real binder rather than deleted, and the
distinction is written down instead of being smoothed over: a matrix that is *complete* is not
thereby a matrix in which every cell *discriminates*.
@@ -105,11 +105,17 @@ The accurate statement is narrower: **no progression or enumerator state is keye
the replace does not reset scheduling position — but two persisted *diagnostic/grouping* fields do
embed them and are orphaned by it. Degradation, not data loss. The evidence originally offered
(`BlockKey` keys off parent id plus `DateUpdated` ticks, never `BlockItem.Id`) is true and supports
only that narrower claim. The handlers that *do* carry progression state already reconcile by id —
`ReplaceProgramScheduleItemsHandler` (#252's scoped positional/no-op reconcile, guarding
`PlayoutScheduleItemFillGroupIndex`), `ReplacePlayoutAlternateScheduleItemsHandler` and
`ReplacePlayoutTemplateItemsHandler`. Those last two operate on `PlayoutTemplate`, NOT on the
`TemplateItem` rows listed among the four above — a distinction worth reading twice.
only that narrower claim. The other full-replace handlers reconcile by id rather than delete-and-reinsert:
`ReplaceProgramScheduleItemsHandler` (#252's scoped positional/no-op reconcile),
`ReplacePlayoutAlternateScheduleItemsHandler` (over `ProgramScheduleAlternate`) and
`ReplacePlayoutTemplateItemsHandler` (over `PlayoutTemplate`) — neither of the last two touches the
`TemplateItem` rows listed among the four above, despite the names.
Only the FIRST of those three is demonstrably protecting identity-keyed state: `PlayoutScheduleItemFillGroupIndex`
holds an FK to `ProgramScheduleItemId`, whereas `PlayoutTemplateId` and `ProgramScheduleAlternateId`
appear nowhere in the solution, model snapshot included (verified 2026-08-21). The other two reconcile
by id for their own reasons, and attributing that to progression state would be a causal story the
schema does not support.
The general lesson is the one this record nearly failed to learn: a universal negative in a durable
document reads as a checked fact and talks the next reader out of checking. Bound it to what was