fix(update): the monitor is this workspace's exact command line; create/resume take the pane workspace's admission; refusal snapshots compare the registry by rows (Issue #72, review round 3)

- daemon.find_running matches `<bash> <reconcile.sh> --subscribe[ ...]`
  exactly instead of an unanchored, unescaped pgrep regex: another
  workspace's daemon (ws.a vs wsXa) or a process naming the path in an
  argument is no longer signalled by the update. update.sh re-checks which
  PIDs are still the monitor before SIGKILL.
- create and resume (spawn path) take the update admission of every
  installed workspace at or above the pane cwd, not only the control
  root's (lib.sh mam_update_admit_at, lib_py.update target-workspaces --cwd);
  the ~/.local/bin wrapper child drops all of them.
- tests: snapshot() skips the SQLite -wal/-shm and compares the DB by its
  rows (macOS keeps the sidecars and a read rewrites -shm); regression tests
  for the monitor match and for create/resume in another installed
  workspace (marker and lock wait); evidence line refs shifted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
GodopuandClaude Opus 5.5 committed 2026-10-28 02:28:55 +09:00
1 parent 454b77215f
commit cf1b693e85
9 files changed
+209 -32

No files matched your search

+24 -9
View File
@@ -2,7 +2,8 @@
Behaviour is byte-for-byte compatible with the shell original (issue #5 phase 1,
D5): same monitor path, same stderr wording and exit status when it is missing,
same `pgrep -f` pre-check pattern, same log file, same "print the PID" contract.
the same daemons found by the pre-check (matched exactly since Issue #72, see
find_running), same log file, same "print the PID" contract.
Callers capture stdout with $( ), so nothing else may be printed there.
"""
from __future__ import annotations
@@ -39,20 +40,34 @@ def monitor_script_path(workdir: str) -> str:
def find_running(monitor_script: str) -> List[str]:
"""PIDs of subscribers already running for this monitor script.
Uses the exact `pgrep -f` pattern the shell helper used, so a daemon started
by either implementation is found by the other. Multiple matches are returned
in full: the legacy helper echoed every line and callers only ever echo it
back, so trimming here would be a silent behaviour change.
A subscriber is a process whose command line is `<bash> <monitor_script>
--subscribe[ ...]`, argv0 a bash, as both this module and the shell helper
start it. Issue #72: matched exactly, not as the shell helper's `pgrep -f`
regex, because the update skill signals what this returns - a daemon of
another workspace the unescaped path also matched, or a process that only
names the path in an argument, is not this workspace's monitor. Multiple
matches are returned in full: the legacy helper echoed every line and
callers only ever echo it back, so trimming here would be a silent
behaviour change.
"""
try:
res = subprocess.run(
["pgrep", "-f", "bash {} --subscribe".format(monitor_script)],
["ps", "-A", "-ww", "-o", "pid=", "-o", "args="],
capture_output=True, text=True,
)
except OSError as exc: # pragma: no cover - pgrep missing
logger.warning("pgrep unavailable: %s", exc)
except OSError as exc: # pragma: no cover - ps missing
logger.warning("ps unavailable: %s", exc)
return []
return [line for line in res.stdout.split() if line.strip()]
want = "{} --subscribe".format(monitor_script)
pids = []
for line in res.stdout.splitlines():
parts = line.strip().split(" ", 1)
if len(parts) != 2:
continue
argv0, _, rest = parts[1].partition(" ")
if os.path.basename(argv0) == "bash" and (rest == want or rest.startswith(want + " ")):
pids.append(parts[0])
return pids
def _ignore_sighup(): # pragma: no cover - runs in the forked child