Commit Graph
332 Commits
Author SHA1 Message Date
timothyandClaude Opus 4.8 2352604836 docs(69): PR1 backend implementation plan
Task-by-task TDD plan for the auto-tuning backend: axis map, number
allocator, preview query + EF enumeration, bulk-create orchestration,
REST endpoints, OpenAPI + docs.

Refs #69

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 22:16:43 +02:00
timothyandClaude Opus 4.8 35ad7df6a4 docs(69): auto-tuning 3-axis MVP design spec
Design for #69 auto-tuning: enumerate library metadata (TV Show / TV
Genre / Movie Genre) -> preview -> select -> bulk-create channels.
Additive/non-destructive, enumerate-via-EF + persist-via-SmartCollection,
server-side query generation, reserve-block numbering.

Refs #69

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 22:16:43 +02:00
timothy b17a70be1b Merge pull request 'fix(spa): body margin + nav box-sizing resets (#373, #377)' (#378) from fix/373-377-spa-css-resets into main
Build ErsatzTV Image / Docs update reminder (push) Has been skipped
Build ErsatzTV Image / decisions.md append-only (push) Has been skipped
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 4m11s
Build ErsatzTV Image / Build & push image (amd64) (push) Has been cancelled
Build ErsatzTV Image / Functional E2E (curl contracts) (push) Has been cancelled
Build ErsatzTV Image / Build & test (.NET) (push) Has been cancelled
2026-07-16 20:12:52 +00:00
timothyandClaude Opus 4.8 1575b9b537 fix(spa): add body margin + nav box-sizing resets (fixes #373, fixes #377)
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 9s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 10s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 16s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 16s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m41s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 6m18s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 6m4s
Two missing-base-reset defects in the ChicoryTV SPA shell, both confirmed by
rendering the real app (not source-reading):

- #373: the browser-default `body { margin: 8px }` was never reset, so it framed
  every full-viewport layout — the app shell and the shell-less `.ctv-auth-page`
  boot pages (login/setup/checking/error), both `min-height:100vh` — with a light
  border on all four edges. Fix: `html, body { margin: 0 }` + paint the app surface
  on `body` so any residual gap/overscroll stays dark (matches the design-system
  reset in forms.card.html / chicorytv-admin templates). Verified body margin 0 and
  no edge border on the boot page and the shell, warm theme, 1280px and 900px.

- #377: the SPA ships no global `box-sizing` reset (default content-box), so
  `.ctv-nav-item { width:100%; padding:0 10px }` overflowed `.ctv-nav`
  (`overflow:auto`) by 20px → a horizontal scrollbar in the sidebar. Fix: scope
  `box-sizing: border-box` to `.ctv-nav-item`. Verified nav scrollWidth==clientWidth
  (216==216) and no horizontal scroll after the fix; the intended vertical nav scroll
  is unaffected.

Docs: spa-conventions.md §1 records the base reset and the deliberate absence of a
global box-sizing reset (set border-box locally when combining width:100% + padding).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 21:57:29 +02:00
timothy 9938d9a6cd Merge pull request '#340: configurable advertised IPTV base URL for M3U/XMLTV' (#374)
Build ErsatzTV Image / decisions.md append-only (push) Has been skipped
Build ErsatzTV Image / Docs update reminder (push) Has been skipped
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 4m3s
Build ErsatzTV Image / Functional E2E (curl contracts) (push) Successful in 3m34s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 9m0s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 6m44s
2026-07-16 19:51:27 +00:00
timothyandClaude Opus 4.8 dd273fbbb2 docs(202-e2e): record completed Plex real-server pin-flow validation
Build ErsatzTV Image / Docs update reminder (push) Has been skipped
Build ErsatzTV Image / decisions.md append-only (push) Has been skipped
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 4m25s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 5m25s
Build ErsatzTV Image / Functional E2E (curl contracts) (push) Has been cancelled
Build ErsatzTV Image / Build & push image (amd64) (push) Has been cancelled
Completes the outstanding Plex flow in the #202 post-merge integration
checklist against a disposable test Plex Media Server: pin-flow auth,
server/library discovery (the 2026-07-13 gap that returned servers:[]),
sync-preference persistence, and sign-out cleanup all passed. Records
exact API state evidence and the test-admin credential reset.

fixes #333

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 21:39:16 +02:00
timothyandClaude Opus 4.8 3bc8192d3b feat(iptv): add configurable advertised base URL for M3U/XMLTV (fixes #340)
ErsatzTV built every absolute M3U/XMLTV URL from the incoming request's
Scheme/Host/PathBase, so a client fetching via a host that downstream
consumers can't resolve (e.g. Dispatcharr over Docker DNS → Kodi) baked
that internal host into programme-image/stream URLs.

Add an optional advertised IPTV base URL, backed by the existing
ConfigElement key/value store (key `iptv.base_url`, no EF migration):

- Central pure Core helper `AdvertisedBaseUrl` (TryParse/Resolve):
  validates absolute http(s), no credentials/query/fragment, preserves
  port + path prefix, normalizes trailing slash. Blank/invalid falls
  back to the request-derived values, so unset output is byte-identical.
- Resolved inside `GetChannelPlaylistHandler` (M3U guide/logo/stream) and
  `GetChannelGuideHandler` (both XMLTV {RequestBase} sites) — controllers
  stay thin, golden tests untouched.
- New `iptv` settings group: GET/PUT /api/v1/settings/iptv (blank clears,
  malformed → 422) + a new IPTV section on the SPA Settings screen.
- Scoped to M3U + XMLTV; HDHomeRun deliberately out of scope. Distinct
  from ETV_BASE_URL (which only sets ASP.NET PathBase).

Tests: AdvertisedBaseUrl unit tests (override/fallback/port/path/invalid),
handler override tests for both generators, settings controller + handler
tests, SPA client + screen tests. Docs: m3u-xmltv, decisions, domain-model,
regenerated OpenAPI v1.json + v1.d.ts + endpoint-index.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 21:08:42 +02:00
timothy 3654fad163 Merge pull request 'ci(coverage): collect code coverage and publish a summary (refs #15)' (#370) from feat/15-ci-coverage into main
Build ErsatzTV Image / decisions.md append-only (push) Has been skipped
Build ErsatzTV Image / Docs update reminder (push) Has been skipped
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 4m21s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 5m40s
Build ErsatzTV Image / Functional E2E (curl contracts) (push) Successful in 6m21s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 3m44s
2026-07-16 17:29:34 +00:00
timothy 9f0bb65fb4 docs(queue): exclude parked selector candidates (#372)
Build ErsatzTV Image / Docs update reminder (push) Has been skipped
Build ErsatzTV Image / decisions.md append-only (push) Has been skipped
Build ErsatzTV Image / Build & test (.NET) (push) Failing after 1m43s
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 / Build & push image (amd64) (push) Has been cancelled
Build ErsatzTV Image / Functional E2E (curl contracts) (push) Has been cancelled
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Has been cancelled
Refs #237
2026-07-16 17:25:10 +00:00
timothyandClaude Opus 4.8 e78aa01c05 ci(coverage): exclude generated EF migrations to stop coverage OOM (exit 137)
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 7s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 8s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m26s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 16s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 17s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 6m5s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 3m46s
The shared Build & test job OOM-killed (exit 137) with coverage enabled: the
whole-solution Architecture.Tests process instruments both EF migration folders
(~2.59M generated lines vs ~200k authored), ballooning coverlet's in-memory
hit-count arrays past the runner's limit.

Add coverlet.runsettings excluding **/Migrations/*.cs (+ generated/compiler
attributes) and pass it via --settings on the Test step. This cuts the
instrumented surface ~126x (2,510,185 -> 19,963 coverable lines in the
Architecture.Tests process, verified locally) and makes the reported percentage
reflect authored code. Documented in docs/ci-cd.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 19:03:54 +02:00
timothyandOpenAI Codex 0c6e8fab01 docs(queue): exclude parked selector candidates
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 9s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 9s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 5m19s
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 9s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 7m36s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 9m55s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Refs #237

Co-Authored-By: OpenAI Codex <codex@openai.com>
2026-07-16 18:58:44 +02:00
timothyandOpenAI Codex 489956b167 fix(api): protect local library path details
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 7s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 8s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 5m30s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 3m28s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 55s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 4m41s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 9m58s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Fixes #334

Co-Authored-By: OpenAI Codex <codex@openai.com>
2026-07-16 18:49:03 +02:00
timothyandClaude Opus 4.8 2090f7865c ci(coverage): collect code coverage and publish a summary (refs #15)
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 9s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 9s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 22s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 36s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m29s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 6m17s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been cancelled
Build ErsatzTV Image / Build & test (.NET) (pull_request) Has been cancelled
#15 scope item 4 ("collect with coverlet and publish a summary; decide on
a floor later"). coverlet.collector was already referenced by four of the
six test projects but the CI Test step never passed --collect and nothing
summarized the result.

- Add coverlet.collector to ErsatzTV.Core.Tests and ErsatzTV.Tests so
  coverage is uniform across all *.Tests projects.
- CI Test step now runs --collect:"XPlat Code Coverage" --results-directory
  ./coverage, then a best-effort Coverage summary step merges the per-project
  Cobertura reports with ReportGenerator (TextSummary to the log,
  MarkdownSummaryGithub to the job step summary). No floor is enforced yet;
  continue-on-error keeps it purely informational.
- gitignore /coverage/; document the step in docs/ci-cd.md (test job).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 18:45:32 +02:00
timothyandOpenAI Codex e8580f4a84 docs(queue): cascade selector through priority tiers
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 11s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 11s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 13s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 8s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 3m12s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m18s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 10m7s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Refs #237

Co-Authored-By: OpenAI Codex <codex@openai.com>
2026-07-16 16:02:03 +02:00
timothyandOpenAI Codex d1a52eaa8b docs(api): use versioned channel routes
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 10s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 11s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 8s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 1m14s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 3m49s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 7m47s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 6m35s
Co-Authored-By: OpenAI Codex <codex@openai.com>
2026-07-16 15:03:53 +02:00
timothyandOpenAI Codex 7dee5194f4 fix(api): report direct streams as on air
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 9s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 10s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 22s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 2m24s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 6m2s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 6m30s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 7m5s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
fixes #99

Co-Authored-By: OpenAI Codex <codex@openai.com>
2026-07-16 14:57:45 +02:00
timothyandOpenAI Codex b39c3b7bb0 docs: make queue selection non-terminal
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 11s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 12s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 14s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 14s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 7m26s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 5m10s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 4m51s
Co-Authored-By: OpenAI Codex <codex@openai.com>
2026-07-16 14:00:16 +02:00
timothyandClaude Opus 4.8 0d7803079c ci: add advisory functional-E2E curl harness (fixes #299)
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 9s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 10s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 5m54s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 17s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 18s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 4m17s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 6m42s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / Docs update reminder (push) Has been skipped
Build ErsatzTV Image / decisions.md append-only (push) Has been skipped
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 4m22s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 5m38s
Build ErsatzTV Image / Functional E2E (curl contracts) (push) Successful in 6m2s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 4m41s
Codify the manual live-E2E curl flows sessions have been re-running by hand
into a CI regression net: a new `functional-e2e` job boots the app from source
(via scripts/e2e-local.sh, parameterized for Release) and drives scripts/
e2e-functional.sh against it.

First-cut contracts (all curl-only, deterministic, no seeded media/ffmpeg/browser):
- legacy->SPA redirect sweep + the /api,/artwork never-redirect exemption
- auth/CSRF/security-stamp flow (setup-claim, read-gate, CSRF, login, logout+revoke)
- library-scan status contract (404/202/scan-status)
- optimistic-concurrency If-Match/412 round-trip

Advisory by design (separate job, not a `build` dependency, not a required
check) so a functional-E2E flake can't block the unit-test gate; promote once
proven, mirroring the migrations-job rollout. SQLite default -> no DB service.

Deferred to #299 follow-ups (need scanner+seeded media or a browser to be
deterministic): the racy 409 re-trigger, playout-build lock 409, Playwright UI
flows.

Assertions verified 30/30 green against a real Release-built instance; caught
/artwork/* returning 400 (not the 404 a static read suggested).

Docs updated same PR: docs/ci-cd.md (new job), docs/e2e-local.md (harness),
docs/decisions.md (append-only).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 13:49:33 +02:00
timothyandOpenAI Codex 805ca026c4 docs(web): define shell and action ownership
Refs #247

Co-Authored-By: OpenAI Codex <codex@openai.com>
2026-07-15 22:51:32 +02:00
timothyandOpenAI Codex 67bfc1ceb1 docs(queue): record cheap-worker launch config
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 7s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 8s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 12s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 12s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Failing after 24s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 7m18s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Record the tested GPT-5.4-Mini low-effort worker command, current spawn-agent limitation, and unsupported legacy model trap so future sessions can route bounded work without rediscovery.

Refs #355

Co-Authored-By: OpenAI Codex <codex@openai.com>
2026-07-14 20:33:21 +02:00
timothyandOpenAI Codex 1e0eaca35e docs(queue): start tool-bearing selectors at low effort
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 9s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 10s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 13s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 13s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m51s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 10m4s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / Docs update reminder (push) Has been skipped
Build ErsatzTV Image / decisions.md append-only (push) Has been skipped
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 4m29s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 5m39s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 3m20s
Clarify that enabled web_search makes a cheap worker tool-bearing even when the selector intends to use only Gitea, reserve minimal for tool-free supplied-evidence synthesis, and require hard evidence/output caps.

Co-Authored-By: OpenAI Codex <codex@openai.com>
2026-07-14 20:03:57 +02:00
timothyandOpenAI Codex 533abe7cdb refactor(spa): extract playouts screen
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 11s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 11s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 17s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 17s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m49s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 6m21s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Move the Playouts UI, route wrapper, helpers, and detailed behavior tests into a colocated screen module while preserving App-level navigation coverage.

Refs #245

Co-Authored-By: OpenAI Codex <codex@openai.com>
2026-07-14 18:54:32 +02:00
timothy e454ed94fd Merge pull request 'docs(queue): enforce milestone-aware picker ranking' (#348) from codex/queue-picker-claim-comments into main
Build ErsatzTV Image / decisions.md append-only (push) Has been skipped
Build ErsatzTV Image / Docs update reminder (push) Has been skipped
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 4m55s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 6m2s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 3m53s
2026-07-14 06:03:46 +00:00
timothyandOpenAI Codex b9305fdee9 fix(spa): preserve remote drafts during save
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 10s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 11s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 12s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 13s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m32s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 9m50s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Disable and gate remote connection draft inputs while a save owns the current revision. Exercise the real App-owned route transition after a successful save so the regression proves the editor unmounts without a dirty prompt.

Refs #344

Co-Authored-By: OpenAI Codex <codex@openai.com>
2026-07-14 00:06:29 +02:00
timothyandOpenAI Codex 3481f98bcc fix(spa): clear dirty guard before saved navigation
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 6s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 7s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 11s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 11s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m29s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 9m44s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Fixes #344

Co-Authored-By: OpenAI Codex <codex@openai.com>
2026-07-13 23:47:43 +02:00
timothyandOpenAI Codex e280e9072c docs(queue): enumerate milestone candidates
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 6s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 7s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 10s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 11s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m25s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 9m40s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Refs #347

Co-Authored-By: OpenAI Codex <codex@openai.com>
2026-07-13 23:31:04 +02:00
timothyandOpenAI Codex d235b91d15 docs(queue): preserve reviewer comment claims
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 6s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 7s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 11s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 11s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m17s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 9m41s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Fixes #347

Co-Authored-By: OpenAI Codex <codex@openai.com>
2026-07-13 23:20:50 +02:00
timothyandOpenAI Codex b2c093d2d6 docs(e2e): record real media-source validation results
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 8s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 9s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 17s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 17s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m26s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 5m28s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / Docs update reminder (push) Has been skipped
Build ErsatzTV Image / decisions.md append-only (push) Has been skipped
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 / Build & test (.NET) (push) Failing after 3m15s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 9m20s
Build ErsatzTV Image / Build & push image (amd64) (push) Has been skipped
Refs #333

Co-Authored-By: OpenAI Codex <codex@openai.com>
2026-07-13 22:51:52 +02:00
timothyandCodex 8da1256ffa docs(workflow): enforce session-wide cost routing
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 10s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 10s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 17s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 17s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m6s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 5m27s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Has been skipped
Build ErsatzTV Image / Docs update reminder (push) Has been skipped
Build ErsatzTV Image / decisions.md append-only (push) Has been skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Has been skipped
Build ErsatzTV Image / Build & test (.NET) (push) Failing after 3m15s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 9m34s
Build ErsatzTV Image / Build & push image (amd64) (push) Has been skipped
Fixes #342

Co-Authored-By: Codex <noreply@openai.com>
2026-07-13 21:43:11 +02:00
timothyandCodex 2609b4ce59 fix: add cross-origin resource policy header
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 7s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 7s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 19s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 1m29s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m15s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 5m26s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / Docs update reminder (push) Has been skipped
Build ErsatzTV Image / decisions.md append-only (push) Has been skipped
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 / Build & test (.NET) (push) Failing after 7s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 3m44s
Build ErsatzTV Image / Build & push image (amd64) (push) Has been skipped
Fixes #330

Co-Authored-By: Codex <noreply@openai.com>
2026-07-13 21:25:06 +02:00
timothyandCodex 80751a5d74 docs: require low-cost queue selection preflight
Build ErsatzTV Image / Docs update reminder (push) Has been skipped
Build ErsatzTV Image / decisions.md append-only (push) Has been skipped
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 4m19s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 5m28s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 3m32s
Co-Authored-By: Codex <noreply@openai.com>
2026-07-13 20:56:23 +02:00
timothyandCodex 985eed072c docs(release): prepare v26.8.0 promotion [decisions-edit]
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 9s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 10s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 12s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 13s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 9m37s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 10m45s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Has been skipped
Build ErsatzTV Image / Docs update reminder (push) Has been skipped
Build ErsatzTV Image / decisions.md append-only (push) Has been skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Has been skipped
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 9m49s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 10m51s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 3m47s
refs #335

Co-Authored-By: Codex <noreply@openai.com>
2026-07-13 19:50:39 +02:00
timothyandCodex 977f9125a5 docs: track #202 real-server integration validation
Build ErsatzTV Image / Docs update reminder (push) Has been skipped
Build ErsatzTV Image / decisions.md append-only (push) Has been skipped
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 4m30s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 5m43s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 3m36s
refs #202 #333

Co-Authored-By: Codex <noreply@openai.com>
2026-07-13 19:05:04 +02:00
timothyandCodex 954aff21f1 docs: make issue queue handoff client-neutral
Build ErsatzTV Image / Docs update reminder (push) Has been skipped
Build ErsatzTV Image / decisions.md append-only (push) Has been skipped
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 / Build & push image (amd64) (push) Has been cancelled
Build ErsatzTV Image / Build & test (.NET) (push) Has been cancelled
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Has been cancelled
Co-Authored-By: Codex <noreply@openai.com>
2026-07-13 18:59:40 +02:00
timothyandClaude Opus 4.8 366d5d316c feat(ci): #314 — repeatable black-box security scan (authenticated ZAP + semgrep)
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 9s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 10s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 15s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 15s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 10m1s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 10m55s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
scripts/security-scan.sh: boots a THROWAWAY container from the image under test
(fresh config volume; never the deployed prod/test container — the active scan
attacks write endpoints), reads its machine key, and runs an authenticated OWASP
ZAP api-scan that imports /openapi/v1.json (all 160 /api/v1 ops) and injects
X-Api-Key on every request via a replacer rule so it reaches the
[RequiresAuthentication] + RequireKeyForReads surface — then a semgrep SAST
cross-check. Wrapped in `timeout` because zap-api-scan can hang in post-scan
cleanup after the report is already written.

docs/ci-cd.md: new 'Security scanning' section (out-of-ecosystem black-box gate,
run on the docker host per-release like migration-smoke, not a per-PR CI job) +
the Microsoft.OpenApi 2.7.5 pin note in dependency management.

refs #314 #197

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 08:28:13 +02:00
timothyandClaude Opus 4.8 216130b4d7 fix(#172): API hardening — null-name 500s, duplicate template items, unreachable 404
Clears the still-live findings from #172 (verified against main; #2/#4/#7 and the
auth/search/Trakt tail were already deliberate-documented or fixed since 2026-07-07).

- Null/empty Name → 500 (10 create/replace handlers). Block/Template/DecoTemplate/Deco
  Create+Replace/Update + UpdateFFmpegProfile did `request.Name.Length > 50` on a
  client-nullable string → unhandled NullReferenceException → HTTP 500 (no global
  exception filter). Now `string.IsNullOrWhiteSpace(request.Name) || .Length > 50` →
  422; also rejects empty/whitespace names, matching the group-create handlers'
  NotEmpty behavior. CreatePlaylist coalesces null→"" at the DTO so it was an
  empty-name persist, not a 500; guarded the same way.
- ReplaceTemplateItems overlap validation iterated with an `item == otherItem`
  record value-equality skip, so two exact-duplicate items were value-equal and
  bypassed the intersection check (both persisted). Now index-based (i != j) so
  duplicates register as a self-intersection and are rejected 422.
- Trimmed the unreachable 404 ProducesResponseType from POST /api/blocks/groups and
  POST /api/templates/groups (a create has no parent lookup that can 404); v1.json
  regenerated.
- Regression tests: all 10 name-guard paths + the duplicate-items path (19 cases).
- Docs: decisions.md entry + api-conventions.md §3b null-safe-validation bullet.

fixes #172

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 01:16:52 +02:00
timothyandClaude Opus 4.8 ef2bd65c27 feat(api): #286 — mount the whole /api surface at /api/v1
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 10s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 10s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 1m12s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 3m4s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m17s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 10m36s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Version every /api route to /api/v1 (251 controller routes + ~24 Location
headers + the scanner callback URL + the Startup request-log literal),
uniform across the machine API, auth, scanner and scripted-build surfaces.

Add ApiVersionRewriteMiddleware: a legacy unversioned /api/* request is
rewritten (NOT redirected) to /api/v1/* in-pipeline — method, body, auth
headers and query survive — carrying RFC 8594 Deprecation/Sunset headers,
so curl / the future MCP server / bookmarks keep working. An already-
versioned path passes through; a future /api/v2 is never forced to v1.

Standardize the route convention (leading-slash absolute route per method,
no class-[Route] — except the two Scanner/Scripted controllers whose ~all
actions share a parametrized {id} prefix), enforced by ApiRouteVersioningTests
(^/api/v\d+/ over the whole Controllers.Api surface; browser-nav
/auth/oidc/login is out of scope).

Regenerate v1.json (160 paths, all /api/v1)/endpoint-index/v1.d.ts; sweep 945
SPA request literals + the test mocks (regex + positional URL parsers). /api/v1
is additive-only after freeze; the legacy-rewrite shim sunsets in ~2 releases
(owner decision) with removal tracked as a Phase-3 follow-up.

Docs: decisions.md 2026-07-13, api-conventions §1/§9, rest-api/spa-conventions/
blazor-route-parity/e2e-local/domain-model.

fixes #286
refs #197

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 00:30:20 +02:00
timothyandClaude Opus 4.8 51b67dea06 fix(#238): review — trakt sub-route no-op + data-driven wiring test
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 9s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 11s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 8s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 7s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 4m59s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 8m35s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / Docs update reminder (push) Has been skipped
Build ErsatzTV Image / decisions.md append-only (push) Has been skipped
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 4m18s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 5m25s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 4m26s
Cold-review follow-ups (both non-blocking):
- Low: the TopBar "Add Trakt List" button was a silent no-op on the
  /app/trakt-lists/{id} detail sub-route (setAddOpen state isn't rendered by
  the editor branch, and the screen is keyed by pathname so the state
  wouldn't survive a navigate). Guard on editingId: route back to the list
  from the detail view, open the dialog from the list.
- Nit: the invariant test only spot-checked 2 screens. Replaced with a
  data-driven it.each over the 4 URL-navigating create screens (channels,
  filler, ffmpeg, watermarks) asserting each banner actually navigates — a
  typo'd route id now fails red. Docs wording corrected to match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 23:35:59 +02:00
timothyandClaude Opus 4.8 612b11589c fix(#238): wire TopBar primary-action on create screens, drop dead buttons
The shell TopBar rendered a primary-action button (Plus icon) for every
screen, but only SchedulesScreen subscribed to its ctv:primary-action event
— so every other screen's button was dead (a labelled no-op, or a bare "+"
for the ~10 routes whose primaryAction was '').

Resolution (issue #238): the Plus-icon button is a "create new item"
affordance. Keep + wire it only on the 8 list screens with a single create
flow (channels, schedules, multi/rerun collections, trakt lists, filler
presets, ffmpeg profiles, watermarks) via a shared usePrimaryAction hook
(web/src/primaryAction.ts); drop it (primaryAction: '') everywhere else —
where the action isn't a create (Save/Refresh/Play/Validate/Reset/Scan, all
of which have correct in-body controls), is ambiguous (collections tabs), a
silent no-op (builder, playlists), or misplaced (dashboard, libraries). The
TopBar now renders the button only when primaryAction is non-empty.

Also relabels the apiKey route's stale post-#295 "Save key"/description.
Docs: spa-conventions.md §10 + decisions.md 2026-07-12.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 23:35:59 +02:00
timothyandClaude Opus 4.8 8090e10408 fix(api): #265 — If-Match evaluates per RFC 7232 (valid-but-non-matching → 412, not 400)
The shared optimistic-concurrency parser (ConcurrencyHeaders.ParseIfMatch) classified any
non-canonical/weak/list If-Match value as Malformed → 400. Per RFC 7232 §3.1 a syntactically
-valid entity-tag that simply doesn't strong-match must be 412; 400 is only for a genuine
grammar violation.

- Rewrite ParseIfMatch as a real RFC 7232 entity-tag/list parser: walks the comma-separated
  1#entity-tag list, validates each [W/]DQUOTE *etagc DQUOTE member, and collects the strong
  members whose opaque text is our canonical decimal. Weak / empty / non-canonical /
  out-of-range tags are valid but contribute no version (→ empty set → 412); genuine grammar
  violations (unquoted, SP-in-tag, unterminated, garbage) → 400.
- Reshape IfMatchCondition.ExpectedVersion : Option<int> → ExpectedVersions : Option<Seq<int>>
  and VersionedAggregateExtensions.CheckVersion → set membership (any strong match proceeds;
  empty set always 412). Threads through 10 replace/update commands + handlers + request
  mappers + 9 controllers.
- No wire-contract change (400 + 412 already declared on every PUT; the field is header-derived
  and internal — no DTO/route/response-type/OpenAPI change).
- Tests: ConcurrencyHeadersTests rewritten for the new classification (lists, weak, empty,
  non-canonical → Version/empty-set; grammar violations → Malformed) + new
  VersionedAggregateExtensionsTests for CheckVersion membership/empty-set/force-write.
- Docs: api-conventions.md §7a rewritten; decisions.md entry appended.

Refs #253 #197
fixes #265

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 23:09:36 +02:00
timothyandClaude Opus 4.8 6537697fe5 test(#319): hash-drift guard prefers the built index.html when present
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 9s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 10s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 7s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 1m7s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 5m14s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 9m36s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Has been skipped
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Has been skipped
Build ErsatzTV Image / Docs update reminder (push) Has been skipped
Build ErsatzTV Image / decisions.md append-only (push) Has been skipped
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 4m33s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 5m19s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 5m14s
Addresses the cold-review LOW: the guard hashed only the committed source
web/index.html, so a hypothetical future Vite transform of the inline script
would leave the test green while the deployed CSP silently broke the SPA. It now
hashes the built wwwroot/app/index.html when it exists (the exact bytes the
browser hashes — full coverage on any local build), falling back to the source
only in a fresh CI checkout where the built artifact is gitignored/absent. Vite
copies the inline script verbatim today, so the two agree. Doc comments +
decisions.md synced. Test-and-docs only; no production code change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 22:41:24 +02:00
timothyandClaude Opus 4.8 873b3e54a5 security(#319): enforcing CSP + Permissions-Policy on the host
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 11s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 11s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 13s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 1m48s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 3m49s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m16s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Completes the CSP the #279 baseline-headers middleware deferred. Surfaced by
the #314 ZAP baseline (missing CSP/Permissions-Policy WARNs); a #197 exit item.

SecurityHeadersMiddleware now also sets Permissions-Policy (deny-all for
camera/mic/geolocation/payment/usb) and an enforcing Content-Security-Policy on
every response EXCEPT /docs (Scalar) and /openapi — those rely on inline
bootstrap scripts/styles a strict policy would break (baseline headers still
apply; hardening that admin surface is a #197 follow-up).

CSP: default-src 'self'; script-src 'self' + the hash of the SPA's inline
theme-bootstrap script (no 'unsafe-inline'/'unsafe-eval'); style-src adds
'unsafe-inline' (React inline styles) + fonts.googleapis.com; font-src adds
fonts.gstatic.com; img-src adds data:/blob:; object-src 'none'; base-uri 'self';
frame-ancestors 'none'; form-action 'self'.

The Google Fonts allowance (the SPA CSS @imports the Geist web font) was caught
by live-E2E, which the static recon missed. A guard test hashes the committed
web/index.html inline script and fails if it drifts from the middleware constant
(the built wwwroot/app is gitignored/absent in CI, and Vite copies the inline
script verbatim). Verified: full test pass, live-E2E (SPA renders clean, zero
CSP violations), curl (CSP present on /app + /api, absent on /docs + /openapi).

Docs: docs/decisions.md entry.

fixes #319

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 22:36:44 +02:00
timothyandClaude Opus 4.8 ec26e1be5b fix(api): #316 review — POST-ify graphics-elements refresh, LockedError→409, no-store machine-key
- GET /api/graphics-elements no longer side-effects; refresh moved to
  POST /api/graphics-elements/refresh (204), closing a CSRF vector on a GET.
- PrepareTroubleshootingPlaybackHandler now returns a typed LockedError from
  both atomic lock-acquire failures; ApiResults.ToErrorResult maps it to 409
  instead of falling through to 422, so a lock lost in the race between the
  controller's pre-check and the handler's atomic acquire still reports 409.
- AuthController.MachineKey sets Cache-Control: no-store + Pragma: no-cache
  on the 200 response carrying the master API key.
- Reworded the stale "subtitleId query parameter" endpoint description now
  that playback/start takes a JSON body.
- Regenerated openapi/v1.json + docs/endpoint-index.md; docs/api-conventions.md
  updated with the LockedError pattern (§3a) and the ToErrorResult table row.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 21:50:42 +02:00
timothyandClaude Opus 4.8 461c763dc6 docs: #295 PR2 + #301 — decisions entry, api-conventions §9, e2e-local browser flow
- decisions.md: new entry (SPA cookie-only cutover, boot-gate-not-route, #301
  POST-ification rationale, machine-key-read + OIDC-logout residual) + TOC line.
- api-conventions §9: #301 resolved (POST-ify) + 'never add a side-effecting GET'
  standing rule; machine-key endpoint added to the auth surface list; PR2-shipped note.
- e2e-local: fix stale 'no key required' claim (fail-closed since #197) + browser
  setup/login boot-gate flow.
(spa-conventions §5e rewrite landed with the SPA-consumers slice.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 21:50:42 +02:00
timothyandClaude Opus 4.8 de9fc7e8cc feat(web): #295 PR2 SPA consumers — troubleshoot POST/blob downloads + machine-key screen + password change
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 21:50:24 +02:00
timothyandClaude Opus 4.8 88d82266b6 feat(api): #301 POST-ify side-effecting troubleshoot GETs + GET /api/auth/machine-key
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 21:50:24 +02:00
timothyandClaude Opus 4.8 4da2b67ab2 feat(ci): #315 migration-on-prod-copy smoke for the release path
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 12s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 14s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 20s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 20s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m25s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 5m36s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
CI's migrations job only proves a migration is well-formed against a fresh,
empty DB. It never exercises the migration — or ErsatzTV's startup data steps
(DatabaseMigratorService -> DbInitializer + PopulatePathHashes over the real
MediaFile table) — against the accumulated prod SQLite, so a migration green on
a fresh DB can still fail/corrupt on prod, found only mid-deploy.

scripts/migration-smoke.sh rehearses it on a THROWAWAY copy of the latest prod
backup: boots the new image against the copy, gates PASS on the "Done applying
database migrations" log line (the migrator is a BackgroundService running
concurrently with Kestrel, so HTTP readiness alone doesn't prove migrations
finished), FAILs on early container exit / migration exception / timeout / not
serving afterwards. Always operates on a copy, never the live DB; tears down its
container + temp dir (incl. the container's root-owned config files) on exit.

Validated live 2026-07-12: :latest vs a copy of the 283MB prod backup ->
migrations applied cleanly, app booted+served, temp dir removed.

Home split: this repo owns the script + docs; wiring it into the Komodo
pre-deploy step is server-management#589 (cross-repo). Docs: docs/ci-cd.md
(Migration integrity), docs/decisions.md (new entry, pure insertion).

fixes #315

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 21:18:16 +02:00
timothyandClaude Opus 4.8 90c8348efe fix(process): #317 docs-only exemption stays passthrough, not silent auto-grant [decisions-edit]
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 9s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 10s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 14s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 13s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m22s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 5m30s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / Docs update reminder (push) Has been skipped
Build ErsatzTV Image / decisions.md append-only (push) Has been skipped
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 4m33s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 5m36s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 3m33s
Adversarial-review nit on the first commit: auto-granting the docs-only exemption
silently self-merges process-control PRs (.claude/.gitea/.husky — including the
gate hook itself) with no prompt and no review, bypassing human-in-the-loop for
exactly the files that control the gate. Restrict auto-grant to the genuinely-
satisfied (a+b+c) merge path; the docs/process exemption reverts to bare exit-0
passthrough (one normal prompt). Corrects this PR's own decisions.md entry
accordingly ([decisions-edit]: a not-yet-merged draft entry, not history).

8 pipe tests green (adds docs-only -> passthrough).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 20:58:26 +02:00
timothyandClaude Opus 4.8 8565f731cd fix(process): #317 merge-consent gate auto-grants on satisfied path (no double-prompt)
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 7s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 8s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 7s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 8s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 4m58s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 8m39s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
The H6/H10 merge-consent hook's satisfied path did a bare `exit 0`, which does
not auto-approve a PreToolUse tool call — it only declines to block, so control
fell through to the normal permission system and the raw MCP prompt still fired.
A ready-to-merge PR was therefore confirmed twice (conversationally + a redundant
mechanical prompt). Emit permissionDecision "allow" (new `grant` decision) on the
satisfied and docs-exempt paths so the derived state IS the consent; deny/ask
unchanged (fail-closed); non-merge methods keep the exit-0 passthrough.

Docs: CLAUDE.md, kickoff HARD CONSTRAINTS, docs/decisions.md (append-only, pure insert).
Verified: 7 pipe tests (satisfied->allow, unticked->deny, stale->deny, red-CI->deny,
no-verdict->ask, no-creds->ask, non-merge->passthrough).

fixes #317

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 20:52:26 +02:00
timothyandClaude Opus 4.8 f1b2521228 chore(process): #312 H12 — session-end issue-qualification audit
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 7s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 7s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 8s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 6s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m31s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 7m9s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
scripts/issue-qualification-audit.sh: lists OPEN issues missing a `priority:` label
(the #237 ranking keys off priority:/gate labels, so an unlabeled issue is invisible
to it). "Fully qualified" = has a priority: label; gate-vs-backlog derives from the
review label / milestone, and a milestone is NOT required (backlog is unmilestoned).
Advisory (exit 1 if any unqualified); fail-open without Gitea creds. Wired into the
kickoff session-end protocol + a lore bullet. Tested live (flagged 2) + no-creds no-op.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 20:21:20 +02:00