Closes #415. Server-derived health object on the channel list + detail DTOs (built-timeline detection, kind-agnostic across all 5 PlayoutScheduleKind; assessable gate keyed to the owning channel's mode), single "Problems" SPA filter with per-fault badges. Supersedes #72's api.channel-health-signal decision. Co-authored-by: Timothy <timothy.look@gmail.com> Co-committed-by: Timothy <timothy.look@gmail.com>
4.7 KiB
Archive — API surface / DTO conventions
Superseded/retired records for /api/v1 DTO shape and channel-resource decisions. See
docs/decisions/archive/README.md for the archive's general rules (rationale kept verbatim, never
in the active read-path). Active successor for channel health: api.channel-health-object in
docs/decisions.md.
2026-07-17 — Channel health on the API = the raw PlayoutCount fact on the list DTO, not a derived status enum (#72)
key: api.channel-health-signal · status: superseded · since: 2026-07-17 · supersedes: none · superseded-by: api.channel-health-object@2026-07-23
Rule: (superseded) Channel health rides ChannelResponseModel/ChannelListItem DTOs as a raw int PlayoutCount fact (free — GetAll already Includes Playouts), not a new endpoint, not /channels/state (runtime-liveness cadence), and not a derived ChannelHealth enum (would freeze policy before the #383/#384 auto-tune status taxonomy lands).
Signals: channel health, PlayoutCount, config-derived vs runtime-liveness cadence, frozen /api/v1 · paths: ChannelRepository.GetChannel, Mapper.GetPlayoutsCount, api-conventions.md §3a · issues: #72, #383, #384, #401
Mechanics: superseded by api.channel-health-object (ersatztv#415); see docs/decisions.md → that record for the health object that replaces PlayoutCount-as-verdict
#72 asks for per-channel status in the Channels list, "especially channels that will fail to play".
Where it lives: ChannelResponseModel (the lean list DTO), not a new endpoint and not
/channels/state. Channel health is config-derived — it changes when someone edits a playout, not
tick to tick — whereas /channels/state is the fast-poll runtime-liveness feed (OnAir = someone is
streaming right now). Folding health into the polled feed would recompute rarely-changing data every
tick and mix two cadences in one DTO; a third endpoint is over-engineering for one derived integer on a
list whose consumer already reads it. Precedent: api-conventions.md §3a stamps the server-derived
IsLocked onto PlayoutListItemResponseModel for exactly this reason. It is also free: GetAll
already Includes Playouts and MirrorSourceChannel.Playouts and was discarding them, so no extra
query and no N+1 on a large lineup.
A raw fact (int PlayoutCount), not a ChannelHealth enum. v1 has exactly one trustworthy negative
signal, and the auto-tune arc (#383/#384) is about to churn the status taxonomy (origin, auto-tune
outcomes) — freezing a server-side enum now guarantees a breaking rev of a frozen-additive /api/v1
surface. PlayoutCount mirrors the long-standing ChannelViewModel.PlayoutCount 1:1, is a fact rather
than a policy, and leaves the SPA to derive 0 ⇒ "No playout" in one predicate.
What v1 deliberately does NOT compute — each was considered and ruled out, so don't "finish" them without reading this:
- Empty schedule behind an existing playout.
EmptyScheduleHealthCheckonly understands ClassicProgramScheduleplayouts. Block, Sequential, Scripted and ExternalJson channels have noProgramScheduleat all, so a badge driven off that query would be silently absent or wrong for four of the five schedule kinds — the #71 "verify a shared primitive covers ALL variants" trap. Needs a per-kind emptiness notion first. - Broken / missing source.
FileNotFound/Unavailableare server-wide media-item counts with no channel attribution; mapping media → collection → schedule → channel is a project, not a field. - User-defined vs auto-generated origin (#72 scope item a). No honest signal exists:
Channelhas no origin column, andChannelPlayoutSource.Generatedis a playout-strategy value that SPA-created blank channels also carry, so it would mislabel them. Requires a new column + a dual-provider migration, and provenance belongs to the auto-tune arc that stamps it at creation. A join through the"Channel Lineups"system playlist group was rejected: it is a heuristic that breaks the moment a user edits the channel. Deferred to a follow-up blocked on the auto-tune backend.
Keeping all three out held #72 to a read-path-only change: no migration, no write-handler live-E2E.
Corrected in passing: ChannelRepository.GetChannel never included Playouts, so
GET /api/v1/channels/{id} reported playoutCount: 0 for every channel, which silently disabled the
channel editor's playout-source guard. Both call sites now share Mapper.GetPlayoutsCount (Mirror-aware).
The related silent server-side coercion of Mirror→Generated (a 200 that discards the caller's intent,
against the §3 "surface it, don't silently filter" rule) is filed as #401, not fixed here.