Closes#415 (deferred from #72 scope item b; sibling #414 already merged as #575).
What
Adds a server-derived health object to ChannelResponseModel (list) and ChannelDetailResponseModel (detail), plus a single "Problems" rollup filter with per-fault badges in the SPA. Supersedes #72's api.channel-health-signal (raw-PlayoutCount-only) decision → new api.channel-health-object.
Because the built timeline is the same shape for all 5 PlayoutScheduleKind values, coverage is correct by construction (the #71 "shared primitive covers all variants" trap can't bite). Assessable gate: on-demand channels suppress the absence signals (NeverBuilt/EmptyUpcoming) — keyed to the owning channel's mode so mirrors of on-demand sources don't false-flag — while presence signals (BuildFailed/BrokenSource) always apply. Rollup: any fault ⇒ Problems; assessable-clean ⇒ Healthy; else Unknown (never a false verdict for an idle on-demand playout).
Server owns the rollup so the SPA and MCP read one authoritative verdict. Cost: one bounded GROUP BY PlayoutId aggregate query (no N+1), read-time (MediaItem.State flips on scan, not build).
Thread the Task 2 upcoming-item aggregate through Mapper.ProjectToResponseModel
and ProjectToDetailResponseModel, add a trailing Health field to
ChannelResponseModel/ChannelDetailResponseModel, and update both API handlers
to fetch contributing-playout ids, call GetPlayoutUpcomingHealth, and pass the
result into the mapper. Regenerate the OpenAPI spec + fixture DTOs affected by
the additive field.
Independent frontier whole-branch review completed; its one substantive finding (mirror-of-on-demand false Problems — suppression must key off the owning channel's mode) was fixed and re-reviewed clean, along with 4 minor cleanups. The rebase onto current origin/main changed only the docs/decisions.md conflict resolution (kept both main's #434/#435 records and this PR's supersession) and regenerated artifacts (byte-identical to a fresh generation); all reviewed C# is unchanged. Local gate green on this head: 1867/1867 ErsatzTV.Tests, build 0 errors, BOM clean, dotnet format verify clean, check:api clean, decisions_validate OK. Awaiting CI (runs 1048/1049) before merge.
Review-verdict: **MERGEABLE** @ 6c90e7caf7a116686a3c371eadc4a73f898aa975
Independent frontier whole-branch review completed; its one substantive finding (mirror-of-on-demand false `Problems` — suppression must key off the *owning* channel's mode) was fixed and re-reviewed clean, along with 4 minor cleanups. The rebase onto current `origin/main` changed only the `docs/decisions.md` conflict resolution (kept both main's #434/#435 records and this PR's supersession) and regenerated artifacts (byte-identical to a fresh generation); all reviewed C# is unchanged. Local gate green on this head: 1867/1867 `ErsatzTV.Tests`, build 0 errors, BOM clean, `dotnet format` verify clean, `check:api` clean, `decisions_validate` OK. Awaiting CI (runs 1048/1049) before merge.
The supersession appended a 'Why superseded' note to the archived #72 record, which
the diff-mode decisions validator (CI 'decisions lifecycle' job) flags as a rationale-prose
change requiring [decisions-edit]. The archive's own rule is rationale-kept-verbatim, and the
'why' is already carried by the superseded-by link, the Mechanics forwarding pointer, the
archive header, and the successor record api.channel-health-object. Drop the note.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(Note: an earlier CI run's decisions-lifecycle job failed on 6c90e7ca — reproduced locally in diff mode, so not the known flake — and was fixed in 4f028120 by keeping the archived rationale verbatim. All checks now pass on this head.)
Review-verdict: MERGEABLE @ 4f02812
(Note: an earlier CI run's decisions-lifecycle job failed on 6c90e7ca — reproduced locally in diff mode, so not the known flake — and was fixed in 4f028120 by keeping the archived rationale verbatim. All checks now pass on this head.)
(Plain-format re-post of the verdict on the current head — the prior comment used markdown bold on the token. All findings resolved: the frontier whole-branch review's one substantive bug (mirror-of-on-demand false Problems) fixed + re-reviewed clean; the diff-mode decisions-validator failure fixed in 4f028120. Local gate green: 1867/1867 tests, build clean, BOM clean, dotnet format clean, check:api clean, decisions-validate OK (diff mode), catalog up to date.)
Review-verdict: MERGEABLE @ 4f02812
(Plain-format re-post of the verdict on the current head — the prior comment used markdown bold on the token. All findings resolved: the frontier whole-branch review's one substantive bug (mirror-of-on-demand false Problems) fixed + re-reviewed clean; the diff-mode decisions-validator failure fixed in 4f028120. Local gate green: 1867/1867 tests, build clean, BOM clean, dotnet format clean, check:api clean, decisions-validate OK (diff mode), catalog up to date.)
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.
Closes #415 (deferred from #72 scope item b; sibling #414 already merged as #575).
What
Adds a server-derived
healthobject toChannelResponseModel(list) andChannelDetailResponseModel(detail), plus a single "Problems" rollup filter with per-fault badges in the SPA. Supersedes #72'sapi.channel-health-signal(raw-PlayoutCount-only) decision → newapi.channel-health-object.How — built-timeline detection (Approach A)
Every fault is derived from what the scheduler has already materialized, not from per-kind config:
Playout.BuildStatus→NeverBuilt/BuildFailedPlayoutItem(Finish >= now) →EmptyUpcoming;PlayoutItem → MediaItem.State ∈ {FileNotFound, Unavailable}→BrokenSource(exact per-channel attribution, no media→collection→channel join)PlayoutCount == 0→NoPlayoutBecause the built timeline is the same shape for all 5
PlayoutScheduleKindvalues, coverage is correct by construction (the #71 "shared primitive covers all variants" trap can't bite). Assessable gate: on-demand channels suppress the absence signals (NeverBuilt/EmptyUpcoming) — keyed to the owning channel's mode so mirrors of on-demand sources don't false-flag — while presence signals (BuildFailed/BrokenSource) always apply. Rollup: any fault ⇒Problems; assessable-clean ⇒Healthy; elseUnknown(never a false verdict for an idle on-demand playout).Server owns the rollup so the SPA and MCP read one authoritative verdict. Cost: one bounded
GROUP BY PlayoutIdaggregate query (no N+1), read-time (MediaItem.Stateflips on scan, not build).Done-when (#415)
PlayoutScheduleKindvalues (unbuilt/on-demand →Unknown, neverHealthy)willNeverPlay→hasProblems, comment updateddocs/decisions.md(supersede) +domain-model.md+api-conventions.md+spa-conventions.mdupdatedVerification
ErsatzTV.Testsgreen (incl. per-PlayoutScheduleKindguard, real-SQLite repo aggregate test, mirror-of-on-demand regression tests); 24 SPA Vitest green.dotnet formatverify clean,check:apiclean,decisions_validateOK.healthserializes on list + detail DTOs; no-N+1 confirmed on a running instance.origin/main; generated artifacts byte-identical to a fresh generation.🤖 Generated with Claude Code
Review-verdict: MERGEABLE @
6c90e7caf7Independent frontier whole-branch review completed; its one substantive finding (mirror-of-on-demand false
Problems— suppression must key off the owning channel's mode) was fixed and re-reviewed clean, along with 4 minor cleanups. The rebase onto currentorigin/mainchanged only thedocs/decisions.mdconflict resolution (kept both main's #434/#435 records and this PR's supersession) and regenerated artifacts (byte-identical to a fresh generation); all reviewed C# is unchanged. Local gate green on this head: 1867/1867ErsatzTV.Tests, build 0 errors, BOM clean,dotnet formatverify clean,check:apiclean,decisions_validateOK. Awaiting CI (runs 1048/1049) before merge.Review-verdict: MERGEABLE @
4f02812(Note: an earlier CI run's decisions-lifecycle job failed on
6c90e7ca— reproduced locally in diff mode, so not the known flake — and was fixed in4f028120by keeping the archived rationale verbatim. All checks now pass on this head.)Review-verdict: MERGEABLE @
4f02812(Plain-format re-post of the verdict on the current head — the prior comment used markdown bold on the token. All findings resolved: the frontier whole-branch review's one substantive bug (mirror-of-on-demand false Problems) fixed + re-reviewed clean; the diff-mode decisions-validator failure fixed in
4f028120. Local gate green: 1867/1867 tests, build clean, BOM clean, dotnet format clean, check:api clean, decisions-validate OK (diff mode), catalog up to date.)Review-verdict: MERGEABLE @
4f02812Review-verdict: MERGEABLE @
4f028120d3