b9f031f52abb3d6f53e4551b045e839728fcf9ea
Round 3, from two independent re-reviews of the round-2 fix commit. The fix for the lost-output-on-SIGTERM defect introduced a worse one than the defect. The signal handler ended in `exit "$?"`, and `$?` at signal time is the status of whatever command was running — normally 0 for a guard still gathering evidence. Measured on the real guard, `prepush-donewhen.sh` SIGTERMed mid-curl with an unticked Done-when box: uninstrumented rc=-15 git ABORTS the push instrumented rc=0 git PUSHES to main That converts a killed guard into consent, in the permissive direction, in the hook that protects `main`. The handler now flushes, restores the default disposition and re-raises the signal, so the process dies the way it would have died. test_output_survives_a_SIGTERM could not see it: it asserts stdout content, never reads the return code, and drives a capture-mode hook so no exit-code guard is ever signalled. Also from this round: - The flush ran TWICE on the signal path (signal handler, then EXIT trap), writing two contradictory exit records for one invocation — a real decision then a phantom no-op — so the report mis-histogrammed the very case the trap was added for. It is idempotent now. - `exec 4<&- 2>/dev/null`: the round-1 blocker pattern again, eight lines below the comment forbidding it. Rather than fix the third instance and wait for a fourth, test_no_exec_in_the_sink_carries_a_STDERR_REDIRECT now enforces it. - The log write leaked `Operation not permitted` to stderr on an unwritable log, because `>> "$file" 2>/dev/null` opens the file BEFORE redirecting stderr. The fail-open test used a path that dies at mkdir and never reached this. - Classification: an unrecognised value is no longer lowercased into a valid decision, exit 2 now dominates a printed `allow` (it is the harness's block channel, and reporting a permit for a refused call is the one direction this log must never be wrong in), and a non-zero exit no longer annotates a real decision with an invented `+error`. - Classification and replay read the same bytes; they disagreed when a hook used fd 4, so the harness saw `deny` while the log recorded `no-op`. - The stderr normaliser rewrote `line N:` globally, collapsing genuinely different diagnostics; it is anchored to the script-path prefix now. - docs 5.4 claimed the two zero rows were "trigger did not fire", justified by unit tests that invoke the scripts directly and therefore prove nothing about harness dispatch. It now separates what is established from what is not, and stops contradicting its own decision record. Refs: #776 Decisions-Edit: yes Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fix(754,757): declare graphicsElementIds + padToNearestMinute, and pin every MCP tool to its OpenAPI contract (#760)
fix(754,757): declare graphicsElementIds + padToNearestMinute, and pin every MCP tool to its OpenAPI contract (#760)
ErsatzTV
ErsatzTV lets you transform your media library into a personalized, live TV experience - complete with EPG, channel scheduling, and seamless streaming to all your devices. Rediscover your content, your way.
How It Works
- Install ErsatzTV: Download and set up the server on your system.
- Add Your Media: Connect your media libraries and collections.
- Create Channels: Design and schedule your own live channels.
- Stream Anywhere: Watch on any device with IPTV and EPG support.
Key Features
- Custom channels: Create and schedule your own live TV channels.
- IPTV & EPG: Stream with IPTV and Electronic Program Guide support.
- Hardware Transcoding: High-performance streaming with hardware acceleration (NVENC, QSV, VAAPI, AMF, VideoToolbox)
- Media Server Integration: Connect Plex, Jellyfin, Emby and more.
- Music & Subtitles: Mix music videos and enjoy subtitle support.
- Open Source: Free, open, and community-driven project.
Documentation
Documentation is available at ersatztv.org.
License
This project is inspired by pseudotv-plex and the dizquetv fork and is released under the zlib license.
