Records what phase 2a actually delivers and, as importantly, what it does not: the shadow runner decides and logs, and nothing commits an inferred completion until 2b. state-transitions.md gains an implementation mapping so each contract row names the code and the test that hold it up, with the inferred-completed row marked as judged-but-not-committed. phase2a-shadow.md is the operations note: the rollback switch, the lock discipline and why transition_seq exists, the herdr wait rules that are easy to get wrong (milliseconds, always name --until, errors on stderr, a timeout is a healthy answer), the fail-closed decision order, and the 2b handoff. task.md records P2A-00..04 with commit evidence, including two corrections to my own plan: pick_pending was already a correct swap rather than a hole, and the engine had to live with the registry writers because putting it in lib_py/loop would have made delegation import a sibling and mix trees under a frozen loop. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UrDNJkHmm18uu7n7JwV5Qd
3.7 KiB
3.7 KiB
📑 Issue #5 Phase 2a — Shadow Watcher 운영 노트
- 문서 버전: Rev.1
- 상태: Phase 2a 완료 (shadow-only: 레지스트리 무기록)
1. 이 단계가 하는 일과 하지 않는 일
| 한다 | 하지 않는다 |
|---|---|
herdr agent wait로 에이전트 상태를 관측 |
관측으로 상태를 바꾸는 일 |
| 증거를 정책에 넣어 "완료 후보인가"를 판정 | 판정 결과를 레지스트리에 커밋 |
판정을 .mam/shadow/<job_id>.ndjson에 기록 |
job 레코드 필드를 추가/변경 |
| 4중 귀속 증거를 shadow 저장소에 보관 | 레코드 스키마 변경 (Phase 2b 결정) |
자동완료는 Phase 2b다. 본 단계의 candidate_approved는 "커밋했을 것"이라는 로그일 뿐이다.
2. 운영 스위치
| 변수 | 기본 | 효과 |
|---|---|---|
MAM_ENGINE_CAS |
1 |
0이면 update_job_status/pick_pending이 Phase 2a 이전의 보존된 본체로 분기한다. 재배포·revert 불필요. |
Shadow 러너는 애초에 쓰기 경로가 없으므로 프로세스 종료만으로 흔적이 사라진다. .mam/shadow/는 레지스트리와 분리되어 있어 삭제해도 job 상태에 영향이 없다.
3. 락 규율 (P2A-01)
with registry_lock: 판정 + 레코드 1회 쓰기 + 알림 큐잉 ← 블로킹 호출 금지
락 해제 후: 감사 로그 flush, 발행 등 느린 작업
감사 mirror가 락 밖으로 나가면서 순서 보장을 잃을 수 있으므로, 전이마다 단조 증가하는 transition_seq를 레코드와 알림에 함께 실어 늦게 도착한 flush도 순서를 복원할 수 있게 했다. 이 형태는 새로 만든 것이 아니라 pick_pending이 이미 쓰던 패턴을 모든 전이로 일반화한 것이다.
4. herdr agent wait 사용 규칙
--timeout은 밀리초다. 코드의 파라미터명은timeout_ms이며 초 단위 형태는 제공하지 않는다.--until은 항상 명시한다. 생략 시 기본 매칭이idle,done,blocked라working이 빠진다.exit 1의 페이로드는 stderr에 있다. stdout은 비어 있으므로 stdout에서 에러를 읽으려 하면 안 된다.- 타임아웃은 건강한 응답이다. 소켓이 답했고 상태가 안 바뀐 것뿐이다. 전송 실패와 구분해야 한다.
- 전송 실패 시 백오프는 1→2→4→8→16초 상한이며, 캐시된 스냅샷을 폐기한다. 단절 구간 이전의 상태로 추론하면 단절이 완료처럼 보인다.
5. 판정 순서 (fail-closed)
- 전송 이상 →
hold_reconnect(증거 자체가 무효) - 다른 세대/다른 pane 신호 →
reject_stale blocked→hold_blocked(사람 대기 중 완료 금지)- 4중 귀속 + 실행 앵커 미충족 →
hold - 비-quiescent 또는 도구 실행 중 →
hold - 리포트 미확정(매니페스트/해시) →
hold - 전부 통과 →
candidate_approved
6. 시나리오 계약
Phase 0의 7종 + Phase 2a의 5종 = 12종이 expected_policy.decision으로 판정을 고정한다. 신규 5종도 Phase 0의 validate_watcher_scenario를 통과해야 하므로 증거 스키마가 계속 강제된다.
7. Phase 2b 인계
attempt_id등 귀속 필드를 레코드로 승격할지 결정 (현재는 shadow 저장소).policy.decide의candidate_approved를engine.transition("completed", expected_attempt=...)에 연결. 엔진과 CAS 표는 이미 준비되어 있다.- Pending Notification 지연 알림·크래시 복구 (
state-transitions.md§5). preexec_fn: Phase 2a는 데몬을 스폰하지 않아 미노출. 2b가 스폰을 도입하면 스레드 안전한 방식으로 교체해야 한다.