release(v4.12.0): update skill with in-place install and an update admission gate; resume starts the monitor (Issue #72) - archive the review reports, bump the lockstep version

The update skill joins the version matrix and the lockstep test's skill list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
This commit is contained in:
GodopuandClaude Opus 5.5 committed 2026-10-28 07:49:53 +09:00
1 parent ec7f5c4e57
commit 56e6713292
26 files changed
+542 -30

No files matched your search

@@ -0,0 +1,14 @@
{
"loop_id": "loop-20261027T201245Z-63c71b",
"base_sha": "1d63e882bc1c46e9af8484647d5091b296d8fa3f",
"head_sha": "ec7f5c4e57286fb3c4238b8220f39094f4640391",
"tree_sha": "fbadb24de60ccdae684b23e2e0a41cdf23ab81eb",
"review_commit": "ec7f5c4e57286fb3c4238b8220f39094f4640391",
"reviewer": "reviewer-grok-01",
"job_id": "a43bca93",
"verdict": "PASS",
"transport": "herdr",
"evidence": "review-tree",
"isolation": "worktree",
"verified_at": "2026-10-27T21:06:27Z"
}
@@ -0,0 +1,71 @@
# PR #75 (Issue #72) 리뷰 — 라운드 5
검증 경로 `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261027T201245Z-63c71b/1` 만 봤다. HEAD `ec7f5c4e57286fb3c4238b8220f39094f4640391`, tree `fbadb24de60ccdae684b23e2e0a41cdf23ab81eb`, base `1d63e882bc1c46e9af8484647d5091b296d8fa3f` (HEAD 의 부모이자 조상), porcelain 0. 스냅샷에는 `.venv` 가 없다. 테스트 뒤에도 HEAD·tree·porcelain 은 같았다. 제어 평면의 입장 잠금과 업데이트 표식은 없었고, 체크아웃 모니터 pid 3714 (`reconcile.sh --subscribe --idle-timeout 0`) 는 그대로였다.
이번 커밋은 `ec7f5c4` 하나다. `1d63e88..ec7f5c4` 는 `lib.sh` 와 `tests/test_issue72_update.py` 이고 `git diff --check` 는 깨끗했다. diff 에 `mapfile`, `declare -A`, `local -n`, `[[ -v`, `sed -i`, `stat -c`, `readlink -f`, `date -d` 는 없다. `setsid(1)` 도 호출하지 않는다.
## 확인한 것
R2. `MAM_DELEGATION_PY=0` 의 기동도 호출자 프로세스 그룹 밖에 둔다.
- `lib.sh:2297-2310`. 이미 떠 있는 모니터가 없을 때만, 호출자가 이미 `-m` 이었는지 기억하고 `set -m` 으로 잡은 뒤 `nohup bash <reconcile.sh> --subscribe --idle-timeout 0` 을 백그라운드로 띄운다. stdin 은 `/dev/null`, 로그는 append, 그 다음 호출자의 `-m` 을 되돌린다. 중복 확인(`lib.sh:2291` `_monitor_pids`)과 `MAM_NO_SUBSCRIBER_DAEMON=1` (`lib.sh:2295-2296`) 은 그 앞에 그대로다. PID 출력은 `lib.sh:2314-2315` 이다.
- 이 함수를 쓰는 재시작은 세 곳이다. 설치 성공은 `post_update.sh:41`, 정지 시간 초과는 `update.sh:106`, 롤백과 상태 기록 실패는 `install_phase.sh:64` 의 `bash -c` 가 복구된 트리의 `start_watchdog` 를 부른다. `lib.sh:2567-2573` 에서 `MAM_DELEGATION_PY=0` 이면 레거시, 아니면 Python `start_new_session` 이다.
- tty 없이 `set -euo pipefail` 인 bash 3.2 에서 `set -m` 으로 띄운 `sleep` 의 PGID 는 그 PID 와 같고 부모 PGID 와 달랐다. 부모가 끝난 뒤에도 그 프로세스는 남아 있었고, 확인 뒤에 그 PID 만 종료했다.
- `test_issue72_update.py:697-725`. `MAM_DELEGATION_PY` 0 과 1, 설치 성공·TERM 무시 후 KILL·롤백 여섯 조합에서 새 모니터의 PGID 가 이 테스트 프로세스 그룹과 다른지 본다. `box.update` 는 프로세스 그룹을 바꾸지 않으므로 그 그룹이 호출자 그룹이다.
정리. 테스트는 패턴으로 죽이지 않는다.
- `tests/test_issue72_update.py` 에 `pkill` 과 `killall` 은 없다.
- `test_issue72_update.py:221-240`. 이 테스트가 `helper` 로 띄운 프로세스 그룹만 `os.killpg` 하고, 이 임시 작업공간의 `reconcile.sh --subscribe` 명령줄과 그 `pgrep -P` 자식만 `os.kill` 한다. 그 다음 10초 동안 그 작업공간 경로를 명령줄에 가진 프로세스가 남아 있으면 실패한다 (`_processes_naming`, `184-193`). 읽는 용도이고, 다른 작업공간의 모니터는 경로가 다르므로 대상이 아니다.
이전 라운드에서 맞던 중복 방지, opt-out, PID 보고, 로그 append 는 이 diff 가 그 순서를 바꾸지 않았다. Python 쪽 세션 분리는 `tests/test_issue5_phase1_delegation.py:697-699` 가 그대로 본다.
## 실행한 테스트
작업 디렉터리는 스냅샷이다. 인터프리터는 제어 평면 `/Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.venv/bin/python` (Python 3.9.6). `--basetemp=/tmp/mam-i72-a43bca93`.
```
env -u HERDR_SOCKET_PATH -u HERDR_PANE_ID -u HERDR_TAB_ID -u HERDR_WORKSPACE_ID \
-u HERDR_SESSION_NAME -u HERDR_SESSION -u WORKSPACE_ROOT -u MAM_ENV_FILE \
-u MAM_JOBS_DIR -u DELEGATE_JOB_LOGS_DIR \
PYTHONDONTWRITEBYTECODE=1 \
/Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.venv/bin/python -m pytest \
-p no:cacheprovider --basetemp=/tmp/mam-i72-a43bca93 \
tests/test_issue72_admission.py tests/test_issue72_attach_hints.py \
tests/test_issue72_installer.py tests/test_issue72_resume_monitor.py \
tests/test_issue72_update.py tests/test_issue8_update_state.py \
tests/test_deploy_layout.py tests/test_issue42_skill_blocks.py \
tests/test_issue26_ints.py tests/test_version_consistency.py \
tests/test_issue5_phase1_delegation.py \
tests/test_issue19_subscriber_daemon_lifecycle.py \
-q --tb=line
```
결과: **314 passed, 1 skipped**, 888.47s, 종료 코드 0. PGID 여섯 조합과 watchdog 수명 주기가 이 안에 들어 있다.
건너뛴 한 건은 `tests/test_issue42_skill_blocks.py:266` (`.claude/skills` 가 이 체크아웃에 없음). 같은 파일만 `--basetemp=/tmp/mam-i72-a43bca93-skip -rs` 로 다시 돌리면 39 passed, 1 skipped, 0.07s 이다.
basetemp 경로를 가진 프로세스는 종료 뒤에 남아 있지 않았다.
## 발견 사항
차단: 없음. 설계를 다시 잡아야 하는 결함도 없다.
비차단:
- 레거시 경로는 새 프로세스 그룹이다 (`set -m`). Python 경로는 `start_new_session` 으로 새 세션이다. 이번 라운드가 요구한 것은 호출자 프로세스 그룹에서 벗어나는 것이고, 둘 다 그렇게 한다. `nohup` 이 SIGHUP 을 무시하는 것은 그대로다.
- `lib.sh:2310`. 모니터를 띄운 뒤 원래 디렉터리로 돌아가지 못하면 함수는 1 을 반환한다. 그 시점에는 이미 `set +m` 이 실행됐고 데몬은 떠 있다. 호출자는 PID 를 받지 못한다. 디렉터리가 사라진 경우에만 해당한다.
- 앞 라운드의 비차단 항목(롤백 재시작이 `mam_monitor_started` 를 호출하지 않음, SIGKILL 자식 PID 재확인, 이미 떠 있는 resume 의 잠금 범위, 호출자의 실행 중 잡이 둘일 때)은 이 커밋이 건드리지 않았고 이번 수정 범위 밖이다.
전용 테스트가 없는 조건:
| 조건 | 코드 | 테스트 |
| --- | --- | --- |
| 레거시 모니터가 호출자와 다른 세션인지 | `lib.sh:2306-2308` 은 프로세스 그룹만 분리 | 이번 테스트는 PGID 만 비교 (`test_issue72_update.py:724`) |
| 앞 라운드 표의 세 조건 | 변경 없음 | 이번 범위 밖 |
## 사용자 확인 필요
없음.
[VERDICT: PASS]