fix(issue-19): close the trap race, select daemons by argv shape, align docs with the polling fallback

Review round 1 (claude PASS, agy PASS) recommendations:
- reconcile.sh: `_sub_stop` falls back to `$!` for the window before
  `_sub_pid=$!` (R-1); lock identity compared as (st_dev, st_ino).
- lib.sh legacy start_watchdog prints nothing when opted out and no daemon
  runs, byte-identical to the python body.
- conftest/test: `_is_subscriber_daemon_argv` selects only
  `bash|sh <root>/.../reconcile.sh --subscribe`, never a brief-carrying
  wrapper that quotes the words or a prefix-colliding sibling root (R-2);
  one new unit test for the selector.
- monitor SKILL.md / VERSIONS.md: the polling fallback checks the lock
  file's presence only and does not exclude a second daemon; measurement
  wording uses the argv-shape criterion instead of raw pgrep.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
This commit is contained in:
GodopuandClaude Fable 5.1 committed 2026-09-13 16:11:53 +09:00
1 parent 4da4a50682
commit c096cb455b
6 files changed
+69 -16

No files matched your search

@@ -76,10 +76,13 @@ per workspace through `lib.sh::start_watchdog` (`lib_py.delegation.daemon`). Tha
- is **one per workspace** — `.mam/monitor.lock` (flock) rejects a second instance, and
`start_watchdog` returns the running PID instead of spawning again;
- **ends on its own only when its workspace is gone** — every wake-up (≤ 5 s on the MQTT
loop, every poll on the broker-down fallback) it checks that the workspace directory
still exists and that `.mam/monitor.lock` is still the inode it holds. A deleted
workspace, a purged `.mam/`, or a re-created lock file makes it disconnect and exit 0;
- **ends on its own only when its workspace is gone** — every wake-up (≤ 5 s) the MQTT
loop checks that the workspace directory still exists and that `.mam/monitor.lock` is
still the `(st_dev, st_ino)` it holds; a deleted workspace, a purged `.mam/`, or a
re-created lock file makes it disconnect and exit 0. The broker-down polling fallback
checks the directory and the lock file's *presence* on every pass — it no longer holds
the lock (the python subscriber released it on exit), so while polling it does not
exclude a second daemon, as before;
- **stops promptly on `SIGTERM`/`SIGINT`** — the bash wrapper forwards the signal to the
python subscriber and exits 143. `SIGKILL` is never required;
- is **not started when `MAM_NO_SUBSCRIBER_DAEMON=1`** — the literal `1` only. Use it when