47 call sites wrote `signals` rows directly, in three shapes: a viewer
kill (pid, rtmp), a root action for cron:root_signals (custom_data.action)
and a cache job (cache = 1, custom_data.type). They now name the intent -
SignalDispatcher::kill / rootAction / cache / cacheBatch - and the
dispatcher writes through a SignalSink. The only sink is LegacySqlSignalSink,
which writes the same rows; the cluster API plan (Phase 4) adds the sink
that turns them into signed commands for API-mode nodes. Phase 0 seam.
Behaviour kept column for column (SignalDispatcherParityTest), with two
deliberate differences:
- Actions the panel wrote as literal JSON with a space ('{"action":
"update_binaries"}', restart_services, reboot) are json_encode()d like
every other one. StatusCommand deletes update_binaries rows by their
json_encode() form, so the panel-queued ones were never cleaned up.
- kill() writes nothing for a pid <= 0: the signals daemon skipped those
kills, but for RTMP it sent a drop for client 0.
The "insert unless an identical cache signal is pending" check that
LineService and StreamProcess repeated is cache(..., once: true).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Es8UhyPS8MNjuYTagqfGWi