Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 5s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 5m55s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 8m24s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Consume the EntityLocker ownership contract (#231/#241: Lock* returns true iff this caller won the slot) at three lock-leak sites surfaced by adversarial-reviewer#20. #233 (F3) — troubleshooting playback: - PrepareTroubleshootingPlaybackHandler: both lock sites now acquire via `if (!LockTroubleshootingPlayback())` (kills the check-then-set TOCTOU) and the empty-media-path Left return releases the lock it acquired — previously it leaked, wedging the status endpoint at "running" forever for a file gone from disk. - TroubleshootController.TroubleshootPlayback: lock conflict is now 409 ProblemDetails (was a bare 404, indistinguishable from a bad id); the Prepare-success -> enqueue window releases the lock if we never hand off to StartTroubleshootingPlayback. #234 (F4 + F5.2) — playout builds: - ExtractEmbeddedSubtitlesHandler: try/finally releases exactly the playouts it locked, on every terminal path (cancellation early-return, swallowed cancellation, any exception) — no more permanent leaks after cancelled mid-extraction, and no cross-release of playouts held by someone else. - BuildPlayoutHandler: skips (logs, returns Right) when LockPlayout returns false instead of building unlocked and cross-releasing the other owner's lock in finally. Tests: handler-level release-discipline tests (Prepare empty-path, Extract cancellation + no-cross-release, BuildPlayout skip + finally-release) via the InMemoryTvContext harness, a TroubleshootController 409 test, and OpenApi contract cases for the m3u8 endpoint's 409. OpenAPI regenerated. All non-vacuous (F3 verified against a negative control). Fixes #233, #234 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>