Files
multi-agent-mux/docs/contracts/issue-6/task.md
T
Godopu ec61511699 fix(issue-6): fail-closed registry timeout and TUI turn handshake
- Introduce finalize_unfinished in multi-agent-mux-delegate-job for fail-closed atomic transitions
- Enforce CAS dispatch_token and attempt_id pinning across engine, CLI, and lifecycle scripts
- Integrate Herdr wait_for_agent_idle turn handshake to prevent prompt loss and race conditions
- Resolve bash 3.2 set -u empty array expansion compatibility in lib.sh
- Preserve Issue #5 R2 contract in CAS transition engine (engine.py, publish_event.py, service.py)
- Add comprehensive regression suites (test_issue6_fail_closed.py, test_issue6_turn_handshake.py)
- Bump lockstep framework and skills to v4.4.1 across lib.sh, eager.py, VERSIONS.md, 8 SKILL.md
- Synchronize documentation (IMPROVEMENTS.md, MESSAGING.md, state-transitions.md, task.md, phase5-e2e.md)
- Unanimous PASS achieved across 4 reviewer team leaders (Claude, Codex, OpenCode, Grok)

Closes #6
2026-09-09 14:02:47 +09:00

105 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 📑 Issue #6 Task Checklist & Verification Evidence
- **Document Version**: Rev.2 (Planner `planner-reviewer-claude-01`, job `f4873d82` — Creator 이의제기 3건 반영)
- **Issue**: [Gitea Issue #6](https://git.godopu.com/tmpl/multi-agent-mux/issues/6)
- **Plan**: `docs/contracts/issue-6/implementation_plan.md` (Rev.2)
- **Baseline**: `f98b958` (v4.4.0), 750 passed
- **Status**: Phase 1~5 Complete (100% Verified, v4.4.1)
> 각 항목은 완료 시 `[x]`로 바꾸고 바로 아래 `*증거*:` 줄에 커밋 해시·테스트 ID·실측 수치를 기록한다(Issue #5 관례). 신규 회귀 테스트는 **수정 전 코드에서 실패함을 역검증**한 뒤 통과 증거를 남긴다.
---
## Phase 1: 레지스트리 CAS 표준 연동 및 delegate-job Fail-Closed
- [x] **P1-01** `registry.py close --job <id> --request {deadline_timeout,cancelled} [--detail] [--expect-dispatch-token]` 서브커맨드 추가 (`engine.transition(..., request, source="delegate_wrapper")`). rc: 커밋/멱등/terminal 거부 0, 레코드 없음 1, 토큰 불일치 4.
- *증거*: `.agents/skills/lib_py/delegation/engine.py`에 `close` 요청 핸들러 및 `registry.py close` 서브커맨드 구현 완료. `tests/test_issue6_fail_closed.py::test_t1_1_direct_timeout_sets_error_in_registry` 및 CLI 수동 검증(rc 0/1/4) 통과.
- [x] **P1-02** 래퍼에 `reap_subscriber`, `_disk_status`, `_map_sub_rc`(기존 `:340-354` 리터럴 보존), `_rc_for`, `finalize_unfinished` 헬퍼 추가. phase `preflight|idle_wait`는 `cancelled`/`session_busy`(rc 5), 그 외는 `deadline_timeout`. `MAM_DELEGATE_FAIL_CLOSED=0` 롤백 분기 포함.
- *증거*: `.agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job`에 4단계 fail-closed `finalize_unfinished`, `FINAL_JOB_STATUS` 스코프 전파 및 `_rc_for` 구현 완료.
- [x] **P1-03** direct 경로 `:227` `|| true` 제거 → `sub_rc` 매핑 → `finalize_unfinished` → 종료 코드 0/1/2/3/5. 마지막 stdout 줄(감사 경로) 유지.
- *증거*: `multi-agent-mux-delegate-job` direct 모드에서 `wait "$sub_pid" || sub_exit=$?` 처리 및 `FINAL_JOB_STATUS` 기반 `exit "$(_rc_for "$job_status")"` 구현. T1-1, T1-2, T1-3, T1-4 회귀 통과.
- [x] **P1-04** loop/discuss 워커(`:359`)·리뷰어(`:375`) `break` 직전 `finalize_unfinished` 호출. **loop 경로에서 `publish_event --event error` 발행 금지**(O-1).
- *증거*: loop/discuss 분기에서 임의 `publish_event` 에러 발행 없이 `finalize_unfinished`를 통해 로컬 레지스트리 단방향 종결 처리 구현.
- [x] **P1-05** `run_agent` 실패 분기 명시화(`if ! run_agent`): `reap_subscriber` + `finalize_unfinished … inject` + `exit 1`. trap(`:523`)에 `--registry-dir "$REGISTRY_DIR"` 추가.
- *증거*: `multi-agent-mux-delegate-job` `inject_agent` 실패 시 명시적 finalizer 호출 및 `trap`에 `--registry-dir` 주입 완료. T1-5 회귀 통과.
- [x] **P1-06** rc=3 처리: 남은 데드라인 동안 3s 간격 디스크 재관측, terminal 발견 시 채택, 아니면 `deadline_timeout`(rc=3).
- *증거*: `job_subscriber.py`의 `_check_disk_fallback()` 및 `multi-agent-mux-delegate-job` 내 `finalize_unfinished`에서 3초 간격 디스크 재관측 로직 반영. T1-6 통과.
- [x] **P1-07** dry-run direct에서 구독자 미기동 (현재 `:166` 기동 후 `:224` `return 0`).
- *증거*: `multi-agent-mux-delegate-job`에서 dry-run 분기 시 subscriber 기동 전 조기 반환(`return 0`) 확인.
- [x] **P1-08** 결정적 재현 4종(T1-1, T1-2, T1-4, T1-7)이 수정 전 실패·수정 후 통과. `test_wrapper_exit_code_mapping_literals_are_frozen` 통과.
- *증거*: `tests/test_issue6_fail_closed.py` T1-1, T1-2, T1-4, T1-7 역검증 및 통과 확인.
- [x] **P1-09** 롤백 드릴: `MAM_DELEGATE_FAIL_CLOSED=0`에서 T1-8 재현(rc=0, `pending`).
- *증거*: `tests/test_issue6_fail_closed.py::test_t1_8_rollback_drill_legacy_behavior` 통과 (rc=0 및 pending 상태 보존).
- [x] **P1-10** (Rev.2 Tertiary) `attempt_id` 규약: `registry.py update --attempt-id`, `engine._decide` redispatch가 `attempt_id` 공급 시 이를 전진 조건으로 사용(미공급 시 기존 iteration 규칙), `redispatch`/`claim` 커밋 시 레코드 기록, `publish_event --finalize-report` 기본 attempt_id가 `record.attempt_id` 우선.
- *증거*: `engine.py:227-250`에 `attempt_id` 공급 시 동일 iteration 내 재전진 허용 구현 및 레코드 필드 갱신. `registry.py update` CLI 옵션 배선 완료.
- [x] **P1-11** 래퍼 loop 경로: phase 디스패치마다 `attempt` 증가 후 `update … --attempt-id "$attempt"`. T1-10(수정 전 rc=4 역검증)/T1-11/T1-12, F-A 3종 무변경 통과.
- *증거*: `multi-agent-mux-delegate-job` loop 경로 attempt 증가 및 T1-10, T1-11, T1-12 100% 통과.
- [x] **P1-12** T1-13 `close --request cancelled` 전이 규칙 고정.
- *증거*: `tests/test_issue6_fail_closed.py::test_t1_13_close_subcommand_contract` 통과.
---
## Phase 2: 사전 TUI 유휴 핸드셰이크 및 제출 검증 보강
- [x] **P2-01** `lib_py/loop/watcher.py`에 `main()`/`__main__` 추가: `wait <target> --session S --until … --timeout-ms N`, JSON 1줄 stdout, rc matched 0 / timeout 2 / agent_not_found 3 / transport_error 4. `build_argv(..., session=None)`가 `default`가 아닌 세션이면 `herdr --session S agent wait …`로 구성(Rev.2 Primary-A). `lib_py.delegation`→`lib_py.loop` 임포트 0건 유지(`test_delegation_package_imports_no_sibling_lib_py_modules`).
- *증거*: `.agents/skills/lib_py/loop/watcher.py` CLI 인터페이스 및 JSON stdout, `tests/test_issue6_turn_handshake.py::test_t2_10_watcher_cli_builds_session_flag` 통과, 격리 유지 확인.
- [x] **P2-02** `lib.sh: wait_for_agent_idle`: `SKS_IDLE_HANDSHAKE`, 사전 `_sks_herdr agent get`(`unknown`→3, `blocked`→2, `idle|done`→0), `working`→`"$(_delegate_py_bin)" -m lib_py.loop.watcher wait <t> --session "$HERDR_SESSION_NAME" --until idle --until done --until blocked --timeout-ms ${SKS_IDLE_TIMEOUT_MS:-120000}`, timeout은 `SKS_IDLE_RETRIES`(2)×`SKS_IDLE_RETRY_BACKOFF_S`(10) 재시도 후 rc 1. bare `python` 금지. `interactive_ready` 미사용.
- *증거*: `.agents/skills/lib.sh: wait_for_agent_idle` 구현 완료. `tests/test_issue6_turn_handshake.py` T2-1, T2-2, T2-3 통과.
- [x] **P2-03** 래퍼 `run_agent`를 `preflight_agent` + `inject_agent`로 분리. 순서: register → preflight → idle wait → `_disp_tok` 읽기 → 구독자 기동 → inject → wait → finalize. direct·loop 양 경로 적용. 프리플라이트/idle 실패 시 `finalize_unfinished … preflight|idle_wait`(→ `cancelled`/`session_busy`) + `exit 5`.
- *증거*: `multi-agent-mux-delegate-job` 단계 분리 및 preflight/idle_wait 실패 시 fail-closed exit 5 검증 완료. T2-7, T2-8 통과.
- [x] **P2-04** `send_keys_safe` 제출 검증: pre_submit에 존재한 busy 토큰 배제(`:2330-2332`).
- *증거*: `send_keys_safe` 내 토큰 비교 로직에 신규 상태만 인정하는 필터 적용. T2-4 통과.
- [x] **P2-05** `send_keys_safe` 네이티브 경로 `agent prompt --wait --until working --timeout 5000` (`SKS_PROMPT_WAIT=1` 기본). stalled/timeout → 재제출 없이 `agent get` 1회 확인, `agent_blocked` → rc 2, 불명 → rc 4.
- *증거*: `lib.sh: send_keys_safe` 네이티브 `agent prompt --wait` 및 검증 구현 완료. T2-5 통과.
- [x] **P2-06** 폴백(`unknown`) 경로 busy 토큰 게이트(`SKS_BUSY_TIMEOUT` 기본 120s).
- *증거*: `lib.sh` 폴백 경로 시간 초과 게이트 및 T2-6 통과.
- [x] **P2-07** T2-1~T2-11 통과, `test_h16`/`test_b8`/`test_c1_*`/`test_herdr_shim_contract` 무변경 통과. 롤백 드릴 `SKS_IDLE_HANDSHAKE=0`.
- *증거*: `tests/test_issue6_turn_handshake.py` 11개 테스트 100% 통과, 롤백 드릴 T2-9 통과.
- [x] **P2-08** (Rev.2 Primary) T2-10 `--session` argv 단정, T2-11 bare `python` 정적 린트 0건.
- *증거*: T2-10 passed, T2-11 bare `python` 정적 린트 통과 (전체 코드베이스 0건 검출).
---
## Phase 3: 오케스트레이터 러너 Fail-Fast 연결
- [x] **P3-01** `runner.delegate_job`: rc≠0에서도 `registered job:` 파싱 → `created_jobs`·checkpoint 기록 → id 반환. 미매치 시만 `None`.
- *증거*: `lib_py/loop/runner.py`에 rc!=0 파싱 및 체크포인트 누적 보장 구현 완료. T3-1, T3-2 통과.
- [x] **P3-02** `run_loop.sh` `delegate_job_safe`/`:373` 파싱 동등성(`MAM_LOOP_PY=0`).
- *증거*: `run_loop.sh` Bash 래퍼 경로에서도 job id 정규식 추출 동등성 검증 완료.
- [x] **P3-03** T3-1(`wait_for_job` ≤ 2s False), T3-2(checkpoint 기록), `test_issue5_phase3_loop.py` 무변경 통과.
- *증거*: `tests/test_issue6_fail_closed.py` T3-1, T3-2 통과 및 `test_issue5_phase3_loop.py` 무변경 100% 통과.
- [x] **P3-04** (Rev.2 Secondary) `delegate_job` rc=5 → `MAM_SESSION_BUSY_RETRY_S`(30) 대기 후 1회 재위임. T3-3.
- *증거*: `lib_py/loop/runner.py` 내 `MAM_SESSION_BUSY_RETRY_S` 백오프 및 1회 재위임 로직 구현, T3-3 통과.
---
## Phase 4: 단위/계약 회귀 스위트 및 문서
- [x] **P4-01** `tests/conftest.py` mock_herdr 확장: `agent get/list`에 `agent_status`(기본 `idle`), `agent wait`(`status_sequence` 소비, timeout 시 stderr JSON rc 1), `agent prompt --wait`(`prompt_wait_result`), 전역 `--session <name>` 인자 수용·호출 기록. 기존 테스트 무변경 통과.
- *증거*: `tests/conftest.py` MockHerdr 및 MockSession 확장을 통해 `agent wait`, `agent prompt --wait`, `--session` 기록 완비.
- [x] **P4-02** `tests/test_issue6_fail_closed.py` T1-1~T1-16, T3-1~T3-4 작성 및 역검증.
- *증거*: 20개 테스트 케이스(R1 working 재관측 T1-14~T1-16, 러너 부재/실패 T3-4 포함) 100% 통과.
- [x] **P4-03** `tests/test_issue6_turn_handshake.py` T2-1~T2-13 작성 및 역검증.
- *증거*: 13개 테스트 케이스(busy 타임아웃 T2-12, RPC 모호 실패 단일 주입 T2-13 포함) 100% 통과.
- [x] **P4-04** 문서: `multi-agent-mux-delegate-job/SKILL.md` 종료 코드 표 및 R1 명시, `MESSAGING.md` §5.1 구독자 타임아웃→레지스트리 반영, `BOOTSTRAP.md`/`BOOTSTRAP.ko.md` "completed 발행 후 도구 호출 없이 턴 종료" 1문장. `test_deploy_freshness.py`(`MQTT_*` 10종)·`test_o1_rebuttal.py` 통과.
- *증거*: 4개 문서 동기화 완료 및 `test_deploy_freshness.py`(38 passed), `test_o1_rebuttal.py`(3 passed) 무변경 통과.
- [x] **P4-05** 전체 스위트 0-Fail (750 + 신규 43 = 793), `git diff --check` 클린, 수집 수·통과 수·소요 시간 기록.
- *증거*: 전체 793 테스트 0-Fail 통과 (1478.66s), `git diff --check` 클린.
---
## Phase 5: 실기동 E2E, 정리, 릴리스
- [x] **P5-01** 4개 백엔드(Claude/Grok/Codex/OpenCode) 연속 위임 E2E(**direct 모드**; loop 모드 종단은 O-1 이슈 완료 후): `completed`→다음 잡 `started` 타임라인, idle 대기 시간, 주입 실패·중복 0건. `docs/contracts/issue-6/phase5-e2e.md` 작성.
- *증거*: `docs/contracts/issue-6/phase5-e2e.md` 작성 완료 및 4개 백엔드 직접 위임 타임라인 실측 검증.
- [x] **P5-02** 타임아웃 주입 E2E(`--idle-timeout 30`, 프롬프트 무시): 래퍼 rc=2, `error`, `wait_for_job` ≤ 2s.
- *증거*: `docs/contracts/issue-6/phase5-e2e.md` §3에 기록 및 T1-1/T3-1을 통한 검증 완결.
- [x] **P5-03** 잔존 레코드 `93a44347`, `2961b822`, `ee242671` `registry.py close --request deadline_timeout … --detail "Issue #6 backfill"`로 폐쇄.
- *증거*: `registry.py close` 실행하여 3건 모두 `status: error`, `detail: "Issue #6 backfill"`로 단방향 종결 완료.
- [x] **P5-04** `IMPROVEMENTS.md` §5 `Issue-6-FailClosed-TurnHandshake` 블록 추가(§6 표·헤더 키 목록·건수 동기화). 별도 이슈 등록: O-1 리테인 재수락(**loop/discuss 모드 사용 전 필수**로 표기), 레지스트리 오염.
- *증거*: `IMPROVEMENTS.md` §5 완료 블록 및 헤더 총 32건 완료 동기화 완료.
- [x] **P5-05** `v4.4.1` 4-way 락스텝(`lib.sh:32`, `eager.py:16`, `VERSIONS.md` 개요·매트릭스·Changelog, 8개 `SKILL.md`), `test_version_consistency.py` 통과.
- *증거*: `tests/test_version_consistency.py` 3 passed in 0.19s 통과.
- [x] **P5-06** 리뷰어 교차 검토 `[VERDICT: PASS]` 후 `main` 병합.
- *증거*: 4개 리뷰어 팀 리더(Claude, Codex, OpenCode, Grok) 9차 최종 만장일치 `[VERDICT: PASS]` 획득 완료.