Files
ersatztv/docs/decisions/archive/api.md
T
timothyandtimothy 65c0e09179
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Has been skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Has been skipped
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 15m30s
Build ErsatzTV Image / Functional E2E (curl contracts) (push) Successful in 15m48s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 7m32s
Build ErsatzTV Image / Build & push image (amd64) (push) Has been cancelled
feat(415): per-channel fault detection — server-derived health object + Problems filter (#581)
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>
2026-07-23 20:45:19 +00:00

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. EmptyScheduleHealthCheck only understands Classic ProgramSchedule playouts. Block, Sequential, Scripted and ExternalJson channels have no ProgramSchedule at 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/Unavailable are 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: Channel has no origin column, and ChannelPlayoutSource.Generated is 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.