fix(api): gate playout mutations on EntityLocker build lock (409) + SPA lock mirror (#215) #225
Closed
timothy
wants to merge 0 commits from
fix/215-entitylocker into main
pull from: fix/215-entitylocker
merge into: :main
:main
:renovate/lucene.net
:renovate/dotnet-monorepo
:renovate/sqlitepclraw.bundle_e_sqlite3-3.x
:renovate/meziantou.analyzer-3.x
:renovate/cliwrap-3.x
:913-scripted-playout-401
:release/v26.15.0-notes
:issue-806-guard-populations
:scratch/767b-poisoned
:scratch/767b-control
:release/v26.14.0-notes
:release/v26.14.0
:docs/510-skill-logo-bug-policy
:fix/510-watermark-resolution-policy
:fix/629-verdict-classifier-falseopens
:fix/609-decisions-edit-token-scope
:issue-135-clear-to-none
:release/v26.12.0-notes
:fix/409b-lastscan-api-parity
:fix/401-updatechannel-mirror-422
:fix/327-playlist-rename-validation
:fix/410-scancancel-log-level
:fix/409-447-librariesscreen-neverscanned
:fix/338-zap-exit-code
:fix/367-plex-budget-message
:fix/310-debom-legacy-cs
:ci/604-lane-rebalance
:feat/388-design-mirror
:feat/247-test-ownership
:feat/247-primary-action
:feat/357-player-owned-playback
:feat/357-jellyfin-plugin-poc
:fix/289-mcp-hardening
:issue58-mcp
:feat/244-channels-extract
:ci/auto-bump-prod-compose
:feat/multi-rerun-collections-api
:feat/collections-api
:feat/quick-wins
:feat/185-docs-part2
:feat/140-collections-screen
:feat/146-channel-edit
:feat/147-classic-ui-link
:issue22-renovate-dashboard
:feat/91-cutover
:feat/63-composite-create
:feat/65-library-browse
:feat/85-epg
:feat/86-schedule-editor
:feat/109-dashboard-data
:feat/99-session-tracking
:fix/dockerfile-node-tag
:feat/59-spa-foundation
:docs/59-ui-redesign-brief
:feat/102-json-guide
:feat/111-schedule-durations
:feat/104-artwork-upload
:feat/103-media-sources-api
:feat/playouts-read-api
:feat/108-health-api
:feat/105-picker-list-endpoints
:issue-97-channel-state-api
:issue42-jellyfin-musicvideos
:issue46-rest-api-error-contract
:dependabot/nuget/ErsatzTV.FFmpeg.Tests/multi-d307a2e06f
:qsv-improvements
:hdr-vulkan-cuda-test
No Reviewers
Labels
Clear labels
ad-hoc
api
bug
ci-cd
content
dependencies
enhancement
frontend
in-progress
jellyfin
parked
priority: high
priority: low
priority: medium
review
security
One-off / ad-hoc work not tracked by a dedicated issue
REST API / HTTP endpoints
Something isn't working
Build, test, deploy pipeline
Channel content / schedules / playlists
Dependency updates (Renovate)
New feature or improvement
ChicoryTV React SPA frontend
Claimed by an active session — do not pick up
Jellyfin tuner / IPTV integration
Excluded from automatic queue pickup; work only when explicitly selected
Adversarial review finding
Security / vulnerability fix
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: timothy/ersatztv#225
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #215 (adversarial-reviewer#18 promoted removal gate: Blazor deletion would erase the EntityLocker safety invariant).
IsPlayoutLocked— new sharedApiResults.ConflictProblem(mirrors NotFoundProblem; Trakt precedent left as-is).reset-allunchanged: its handler already skips locked playouts.PlayoutListItemResponseModel.IsLockedstamped from the locker; SPA disables Reset/Erase/Delete on locked rows, shows a Building… badge, and refreshes on 409. No new polling.ErsatzTV.Tests 1003/1003; web tsc + 478/478 + lint + build + check:api clean.
🤖 Generated with Claude Code
Blazor disabled per-playout Reset/Erase/Delete/Edit while a BuildPlayout was in flight (EntityLocker.IsPlayoutLocked); the REST API had no equivalent, so a client could race an in-flight build with a destructive ExecuteDelete and leave a half-built playout. After Blazor removal this safety invariant would vanish entirely (adversarial-reviewer#18 removal gate). Server: - Add public ApiResults.ConflictProblem(title, detail) (409, mirrors NotFoundProblem). - Inject IEntityLocker into PlayoutController; guard every id-keyed mutation (PUT {id}, PUT .../deco, PUT .../alternate-schedules, PUT .../templates, POST .../erase-items, POST .../erase-items-and-history, DELETE {id}) → 409 when IsPlayoutLocked(id); add [ProducesResponseType(...409)] to each. - Guard ChannelController.ResetPlayout the same way after resolving the id. - reset-all stays 202 (ResetAllPlayoutsHandler already skips locked playouts). - Stamp IsLocked onto PlayoutListItemResponseModel from IsPlayoutLocked. SPA: - Disable Reset/Erase/Erase-and-history/Delete for a locked row + show a "Building…" Badge; on a 409 surface the error and refresh the list. Tests: controller-level 409 guard tests (delete/erase/PUT/deco/channel-reset) + IsLocked projection test; new OpenAPI contract + metadata 409 rows. Docs: api-conventions §3a, blazor-route-parity playouts verdict, decisions.md. Regenerated v1.json + web types. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>Merged to main via integration PR #239 (all six gate-batch branches integrated in one CI round after pairwise doc conflicts; this PR's commits are reachable from main — see the merge commits there). Closing manually since Gitea did not auto-detect the branch merge.
Pull request closed