feat(discord): systemd user service with a supervised run; brakes exit 3 and are never retried (#1509)

QUEUE row 17, MVP iteration 2. scripts/discord-service.sh renders and
installs mosaic-discord@<binding> from packages/discord/systemd/. The
unit's main process is `run --supervised`, which applies the new recover
policy first: a lock whose owner is gone is cleared and only the STOP
written for that is removed; an operator STOP or a held binding refuses
with exit 3, which RestartPreventExitStatus never retries. `recover` is
also a CLI verb. First cut used ExecStartPre and looped live, since systemd
honours the never-retry status only from the main process; replaced and
re-verified before any message traffic. Suite 40/40, 95 node tests.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
2026-09-13 14:39:11 -05:00
co-authored by Claude Fable 5.1
parent dc5902aafd
commit 436ba6ed6b
15 changed files with 630 additions and 30 deletions
@@ -421,3 +421,17 @@ Recorded here because they refine a ruling or fill a gap the rulings left.
is logged and recorded in the turn record as `receipt.ok: false` and never
fails the turn. Needs the Add Reactions permission on the bot role.
- Live check 2026-09-13 19:21 UTC: reaction seen by Jason ("test is successful") and recorded in the turn record. Iteration 1 closed.
- Service unit (iteration 2, Jason: "proceed as suggested", 2026-09-13).
`scripts/discord-service.sh` renders and installs a systemd user unit,
`mosaic-discord@<binding>`, from a template in `packages/discord/systemd/`.
The new `recover` policy runs first inside `run --supervised`, the unit's
main process: it clears a lock whose owner is gone and removes only the
STOP it wrote for that; every operator STOP and every held binding
refuses with the new exit code 3, which the unit never retries. First
cut used an `ExecStartPre`; systemd only honours the never-retry status
from the main process, so that looped, and the live check caught it. `systemctl --user stop` is the soft stop (SIGTERM, no STOP,
restartable); `scripts/discord.sh stop` stays the brake. This ends the
hand-removal of STOP after every rerun that step 3 of the pilot showed.
Not done: a binding reload without a restart (a ceiling or channel change
still needs stop and start). Next: the control board row.
- Live check 2026-09-13 19:35 to 19:40 UTC: Sage seat migrated to the unit; SIGKILL recovered in 16 s with the dead lock cleared; `discord.sh stop` held (start exits 3, no restart); released and READY. Iteration 2 closed.