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

57 lines
4.7 KiB
Markdown

# 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 `Include`s `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 `Include`s `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.