# task-2642 — CI_WATCH_HANDOFF_RUNNER (회장 verbatim 즉시 후속 · 정책 코드화)

- Level: Lv.4 (CI/Gemini watcher 자동화 runner · session-independent · ANU 직접 대기 금지 정책 구현)
- 담당: dev6 페룬
- base: origin/main **0e172435** (PR #144 task-2641 OWNER_GEMINI_TRIGGER_ROUTER merge 완료 반영 · 2026-05-23 19:47 KST)
- 단일소스 spec: `memory/specs/system_ci_watch_handoff_runner_spec_260523.md`
  - sha256: `2de5dd98de0ddfee4d5159defaf937d6a468be0202bf74e238de0c5dfe883cc6`
  - 정독 전 sha256sum 검증 필수
- 정책 단일소스: `memory/specs/system_ci_watch_handoff_policy_spec_260523.md`
  - sha256: `e665eda23fa64be644493574a33213f9f3430cf06d09c7fe4927bf90d488cb11`
- feedback 박제: `memory/feedback_anu_no_direct_ci_watch_use_handoff_260523.md`
- 회장 결정 (2026-05-23 19:38 KST): ANU 직접 CI/Gemini 대기 금지 + PR open 후 watcher 위임 강제 → 본 task = 정책 코드화

## 목표
ANU 가 직접 대기하지 않고, PR open 이후 watcher bot/cron 이 terminal state (MERGE_READY / CHAIR_REQUIRED / GEMINI_EXTERNAL_TRIGGER_STALE / CI_FAILED_NON_REMEDIABLE / LOOP_BOUNDARY) 까지 책임지게 하는 runner 구현.

## 회장 verbatim 정책 핵심 (★ task md 정독 + spec 정독 필수)
1. **ANU 는 CI/Gemini 를 직접 기다리지 않는다** (PR open 이후 위임 강제)
2. CI_WATCH_HANDOFF 12 필수 필드 (pr_number / head_sha / branch / expected_files / forbidden_paths / watcher_owner / max_watch_minutes / poll_interval_seconds / gemini_nudge_policy / auto_remediation_policy / callback_on_terminal_state / terminal_states)
3. terminal_states 5 enum (MERGE_READY / CHAIR_REQUIRED / GEMINI_EXTERNAL_TRIGGER_STALE / CI_FAILED_NON_REMEDIABLE / LOOP_BOUNDARY)
4. Watcher 5 단계 (dev bot PR=dev bot watcher / ANU PR=별도 watcher 즉시 발사 / terminal 까지 polling / callback 발사 / ANU 는 callback 후에만 consolidated report)
5. Gemini fresh 부재 시 watcher 가 PR #144 OWNER_GEMINI_TRIGGER_ROUTER 사용 (회장 UI 최후수단)
6. 자동수렴 책임 = watcher (expected_files 내부 medium/style/quality/non-critical HIGH) / ANU callback 보고 = Critical7+credential+밖+admin override+smoke fail

## 신규 helper 3 (utils/ 영역)
- `utils/ci_watch_handoff_runner.py` (state machine · CI/Gemini polling · auto-remediation · ANU callback 발사)
- `utils/ci_watch_handoff_schema.py` (CI_WATCH_HANDOFF JSON v1 schema · 12 필드 + 5 enum validator)
- `utils/ci_watch_handoff_audit.py` (JSONL audit · watcher 주체/schedule_id/terminal state 기록)

## 기존 stack 무수정 (회장 verbatim)
- `anu_v2/owner_gemini_trigger_router.py` (PR #144 stack · 재사용만)
- `anu_v2/gemini_evidence_freshness_checker.py` (PR #144 · 재사용)
- `anu_v2/owner_gemini_trigger_router_audit.py` (PR #144 · 재사용)
- `anu_v2/owner_trigger_only.py` 등 4종 (PR #98 stack · 재사용)
- forbidden 15종 (task-2640 merge 후 stack)

## 신규 fixture 6 시나리오 × 3 = 18 file
`tests/fixtures/ci_watch_handoff_runner/<scenario>/{evidence,expected,PROVENANCE}`:
- `merge_ready_clean_gemini_fresh` → MERGE_READY
- `gemini_stale_nudge_posted_re_poll_fresh` → MERGE_READY (nudge POSTED → fresh 도착)
- `gemini_stale_after_nudge_timeout` → GEMINI_EXTERNAL_TRIGGER_STALE
- `ci_failure_auto_remediation_medium_fix` → MERGE_READY (medium fix → re-push)
- `forbidden_path_modification_detected` → CHAIR_REQUIRED (Critical7)
- `loop_boundary_three_high_attempts` → LOOP_BOUNDARY

## 신규 regression 4
- `tests/regression/test_ci_watch_handoff_schema.py` (12 필드 + 5 enum)
- `tests/regression/test_ci_watch_handoff_runner.py` (state machine + terminal classification)
- `tests/regression/test_ci_watch_handoff_audit.py` (JSONL append + redaction)
- `tests/regression/test_ci_watch_handoff_runner_fixture_parametrized.py` (6 fixture)

## 안전 불변식
- ANU key `c119085addb0f8b7` 단일 출처 유지
- OWNER_GEMINI_TRIGGER_TOKEN 단일 출처 (BOT_GITHUB_TOKEN 사용 0)
- envelope UTF-8 ≤3900 bytes / callback prompt ≤2800 권장
- live cokacdir / gh CLI 실호출 0 (regression mock)
- merge/push/PR/admin override 0 (watcher 만 push/thread resolve)
- real auto-merge activation 0 · PR #141 pilot 재시도 혼합 0 · foreign dirty 정리 0
- production service task 혼합 0 · ANU 직접 코드 구현 0
- forbidden 15종 + owner_trigger 4종 + owner_gemini_trigger_router 3종 무수정
- BLOCKING_SECRET 0

## 필수 regression
- 신규 4 regression PASS
- 기존 baseline (PR #144 merge 후 main 기준) 유지
- full new fail 0 (3 pre-existing test_stash_origin_audit_compat 무관)

## 자동수렴
- Gemini medium/style/quality + expected_files 내부 → 자동수렴
- 동일 함수 HIGH 반복 시 LOOP_BOUNDARY → 회장 보고
- 회장 보고 트리거: Critical7 / credential expansion / expected_files 밖 / admin override / post-merge smoke fail

## 금지 (회장 verbatim)
- ANU "CI 완료 대기 백그라운드 진입" 보고 금지
- session-bound background task 만 믿고 자동수렴 약속 금지
- 회장 입력 기다리는 CI watcher 운영 금지
- 회장에게 "나중에 확인해달라" 기본 경로 요구 금지
- real auto-merge activation 금지
- PR #141 pilot 재시도와 혼합 금지
- foreign dirty 정리 금지
- finish-task.sh / cokacdir / replacement_pr_runner 수정 금지

## expected_files (task-2642 범위)
신규:
- `utils/ci_watch_handoff_runner.py`
- `utils/ci_watch_handoff_schema.py`
- `utils/ci_watch_handoff_audit.py`
- `tests/fixtures/ci_watch_handoff_runner/<6 시나리오>/{evidence,expected,PROVENANCE}` (18 file)
- (선택) `tests/fixtures/ci_watch_handoff_runner/INDEX.md`
- `tests/regression/test_ci_watch_handoff_schema.py`
- `tests/regression/test_ci_watch_handoff_runner.py`
- `tests/regression/test_ci_watch_handoff_audit.py`
- `tests/regression/test_ci_watch_handoff_runner_fixture_parametrized.py`

총 ~27 file.

## finalize 프로토콜 (★ BOT App token 부재 — 로컬 한정)
1. base = origin/main 최신 clean (PR #144 merge 후 진입 권장)
2. 신규 helper 3 + fixture 19 + regression 4 PASS · 기존 baseline 유지 · full new fail 0
3. **로컬 commit 만** (push/PR/merge 금지)
4. ANU normal callback (★ 본 task = watcher policy 자체 자기검증 강제):
   - validate_spawn_callback_contract 호출 self-check (task-2640 결선 active)
   - envelope 5축 + canonical_root=/home/jay/workspace 명시
   - REGISTERED + schedule_id non-null + DELIVERED + UNCONFIRMED
   - envelope UTF-8 ≤3900 bytes (printf '%s' "$P" | wc -c · NOT wc -m)
   - result.json 에 callback prompt UTF-8 byte 수 기록 강제
5. ANU collector key: c119085addb0f8b7 (단일 출처)
6. executor 시작/종료 ts + 로컬 commit SHA 명기

## frozen anchor
- ANCHOR-1: "본 task = ANU 직접 CI/Gemini 대기 금지 정책 (2026-05-23 회장 verbatim) 코드화 · CI_WATCH_HANDOFF_RUNNER 신설"
- ANCHOR-2: "12 필수 필드 + 5 terminal_states + Watcher 5 단계 1:1 박제"
- ANCHOR-3: "PR #144 OWNER_GEMINI_TRIGGER_ROUTER stack 재사용 (무수정) + 기존 owner_trigger 4종 + forbidden 15종 무수정"
- ANCHOR-4: "state machine: PR_OPEN → poll loop → terminal → ANU normal callback 발사"
- ANCHOR-5: "자동수렴 = watcher 책임 (expected_files 내부 medium/style/quality/non-critical HIGH) · CHAIR_REQUIRED = Critical7+credential+밖+admin override+smoke fail"
- ANCHOR-6: "OWNER nudge 1회 hard limit · 회장 UI 최후수단 · NUDGE_403 = CHAIR_REQUIRED permission"
- ANCHOR-7: "envelope ≤3900 bytes · callback prompt ≤2800 권장 · live 실호출 0 (regression mock)"
- ANCHOR-8: "real auto-merge 0 · PR #141 pilot 혼합 0 · foreign dirty 0 · ANU 직접 코드 구현 0"

## callback envelope byte-limit 정책 (★ task-2612+3 박제 강제)
- callback prompt UTF-8 ≤3900 bytes hard limit · 3500+ warning
- envelope 만 포함 (긴 보고 금지) · 상세는 result.json/report.md 위임
- 측정: `printf '%s' "$P" | wc -c` (wc -c · NOT wc -m)
- result.json 에 callback prompt UTF-8 byte 수 기록 강제
