# task-2724+2 — terminal_state_callback DEFAULT_ANU_KEYS empty StopIteration 방어 (회장 B안 bounded robustness fix)

- 작업 ID: task-2724+2
- 팀: dev1-team (헤르메스/팀장, 불칸/백엔드)
- 상태: CHAIR_APPROVED_B_ROBUSTNESS_FIX — 구현/검증 완료, PR #170 갱신(new head `e1254464`), **MERGE_READY_CANDIDATE** (merge 금지)
- PR: #170 (https://github.com/Jeon-Jonghyuk/dev_workspace/pull/170)
- 브랜치: `task/task-2724-dev1` (b7ad94d4 → **e1254464**, non-force push)
- 워크트리: `/home/jay/.cokacdir/workspace/881B3CC7/wt-2724-dev1`
- 작성일: 2026-06-03
- 모델 사용 기록: 불칸(백엔드)=sonnet(구현+테스트), 헤르메스(팀장)=opus(설계/검증/통합/커밋·푸시·QC). haiku 미사용.

---

## SCQA

### S - Situation
PR #170(head `b7ad94d4`)의 잔여 Gemini HIGH: `scripts/harness/v36/terminal_state_callback.py`의 `_register_callback` line 283 `owner_key = next(iter(DEFAULT_ANU_KEYS))`가 `DEFAULT_ANU_KEYS` 공백(비정상) 시 `StopIteration`을 던져 emit 중간 crash → terminal envelope의 `callback_registered` 미갱신 + registration marker 미생성. terminal_state_callback은 실패/차단 통지 안전망이므로 key set 비정상에서도 죽으면 안 됨.

### C - Complication
emit은 envelope를 디스크에 먼저 기록(line 451-452)한 뒤 `_register_callback`(line 455)을 호출하므로, StopIteration은 envelope 기록 이후·marker 기록 이전에 발생한다. `main` fail-open wrapper가 예외를 흡수해도 registration marker 부재로 "왜 미등록인지" 추적이 불가하다. expected_files 2개(소스+테스트) 내부에서 finish-task.sh 추가 변경 0·flag default-off·실제 cron 0을 유지하며 1줄 가드 + 명확한 terminal result 기록이 필요하다.

### Q - Question
회장 6 수정 방향(`next(iter(.., None)` 가드 + fail-closed NO_OWNER_KEY 기록)으로 StopIteration을 구조적으로 제거하고, 회장 10 필수 검증을 전부 통과하며, 기존 19 회귀를 무손상으로 유지할 수 있는가?

### A - Answer (1줄 가드 + fail-closed marker)
1. line 283: `next(iter(DEFAULT_ANU_KEYS))` → `next(iter(DEFAULT_ANU_KEYS), None)` (StopIteration 구조적 불가).
2. `owner_key is None` 분기 신설: `launch_callback` 미진입 → `registered_marker`에 `status=NOT_REGISTERED`, `reason=NO_OWNER_KEY`, `detail` 기록 후 `return False`. envelope는 디스크 잔존(callback_registered=False).
3. 기존 self-key guard / owner 검증 / argv 존재 확인 / sendfile-only 금지 / `TERMINAL_CALLBACK_ENABLED` default-off **전부 유지**.
4. ANU key 리터럴 노출 0(sealed import 유지). 테스트는 dry_run/주입 runner/fail-closed로 실제 cron 등록 0.

---

## 수정/생성 파일 목록

- `scripts/harness/v36/terminal_state_callback.py` (수정, +17/-1) — StopIteration 방어 가드 + NO_OWNER_KEY fail-closed marker
- `tests/regression/test_terminal_state_callback_2724.py` (수정, +92/-2) — TC-20~23 회귀 4종 + docstring 19→23
- `memory/reports/task-2724.md` (갱신) — task-2724+2 robustness 섹션 추가
- `memory/reports/task-2724+2.md` (신규, 본 보고서)

## 수정 파일별 검증 상태 (planned/verified)

- scripts/harness/v36/terminal_state_callback.py:283 — `next(iter(DEFAULT_ANU_KEYS), None)` 가드 — grep OK — **verified**
- scripts/harness/v36/terminal_state_callback.py:285-301 — `owner_key is None` → NO_OWNER_KEY fail-closed marker + return False — grep "NO_OWNER_KEY" OK — **verified**
- tests/regression/test_terminal_state_callback_2724.py:559-643 — TC-20~23 — pytest 23 passed — **verified**
- tests/regression/test_terminal_state_callback_2724.py:1 — docstring 19→23 — grep "23 acceptance" OK — **verified**

planned 항목 0건.

---

## 회장 10 필수 검증 결과

1. ✅ 정상 1원소 → 기존 registration flow 유지 (TC-23 PASS, status=REGISTERED, 주입 runner 1회).
2. ✅ empty mock → StopIteration 0 (TC-20 PASS + L1-B 실제 실행 crash 0).
3. ✅ empty → 실제 cron 등록 0 (TC-22 PASS: launch_callback 미호출 + cokacdir backend 0).
4. ✅ envelope/marker fail-closed NO_OWNER_KEY 명확 기록 (TC-21 PASS + L1-B marker 확인).
5. ✅ 기존 19 regression 유지 (총 23 passed = 19 + 4).
6. ✅ 이번 라운드 `git diff HEAD` = 정확히 2파일. origin/main diff 3파일(2 + 이전 커밋 e78053f9의 finish-task.sh hook = PR #170 기존 내용).
7. ✅ finish-task hook 추가 변경 0 (`git diff HEAD -- scripts/finish-task.sh` 빈 출력).
8. ✅ `TERMINAL_CALLBACK_ENABLED` default-off 유지 (`:-0`, finish-task.sh line 35 flag-guarded).
9. ✅ test cron registration 0 (모든 테스트 dry_run/주입/fail-closed).
10. ✅ ANU key 리터럴 source 노출 0 (`c119085addb0f8b7` not in source).

## L1 스모크테스트 결과 (필수 기록)

- 서버 재시작: 해당없음 (callback emitter CLI 모듈 — 서버/프론트 아님; DIRECT-WORKFLOW Step 4.8 subprocess 경로 적용).
- API 응답 확인: 해당없음 (HTTP API 아님).
- subprocess 실제 실행 (L1-A): `python3 terminal_state_callback.py emit ... --dry-run` (PYTHONPATH=worktree root) → exit 0, envelope(terminal_state=UNKNOWN_FINISH_FAILURE, schema=terminal_state_envelope_v1, callback_registered=False) + registration marker(status=NOT_REGISTERED, reason=fail-closed) 디스크 기록 확인. 실제 cron 등록 0.
- subprocess 실제 실행 (L1-B, 핵심 수정 검증): empty DEFAULT_ANU_KEYS mock으로 emit 실제 호출 → **StopIteration crash 0**(EMIT_RETURNED_NO_CRASH) + NO_OWNER_KEY marker(`{"status":"NOT_REGISTERED","reason":"NO_OWNER_KEY",...}`) 기록 확인.
- 스크린샷: 해당없음 (CLI/subprocess 모듈).
- smoke 명령(task 지시): `python3 -m pytest tests/regression/test_terminal_state_callback_2724.py -q` → **23 passed**; `bash -n scripts/finish-task.sh` → SYNTAX_OK(무변경).

---

## 발견 이슈 및 해결 (Zero Issue = Red Flag 대응)

1. **TC-22 원본 JSON 직렬화 crash (불칸 발견·해결)**: 지시서 TC-22 원본은 `subprocess.run`을 bare MagicMock으로 패치 → failure 경로의 `_get_head_sha`가 MagicMock stdout 반환 → envelope `json.dump`에서 `TypeError`. STEP 1 가드 진입 이전 단계라 본 수정과 무관. → 기존 TC-16/18 패턴(serializable `stdout="sha"` 반환 + cokacdir 호출 카운터)으로 조정, 단언 의미(launch_callback 미호출 + 실제 cron 0) 동일 유지. 23 passed 달성.
2. **L1-A 초기 ModuleNotFoundError(dispatch)**: CLI 직접 실행 시 worktree root가 sys.path에 없어 sealed import 실패. 운영(finish-task.sh)은 WORKSPACE cwd/PYTHONPATH 컨텍스트에서 실행되므로 환경 이슈. PYTHONPATH=worktree root 설정 후 재실행하여 정상 동작 확인. 코드 버그 아님.
3. **pyright reportMissingImports(dispatch) + unused var**: 위 2번과 동일 path 해석 한계 + 기존 코드(`_success`, 테스트 fixture)의 unused. 런타임 sealed import는 정상 동작(L1 검증). 가드 외 변경 금지 범위라 기존 구조 유지. gate 모드 pyright_check는 PASS.
4. **QC file_check 초기 FAIL**: 보고서를 task-2724.md에만 append하여 QC가 찾는 `reports/task-2724+2.md` 부재 → file_check MISSING. 본 보고서(task-2724+2.md) 생성으로 해소.

---

## 머지 판단

- **머지 필요: No** (merge 금지 — 회장 verbatim). ANU/watcher가 new head `e1254464` OWNER /gemini review → CI/Gemini watcher → CI GREEN + fresh unresolved 0 + diff 2파일(+기존 hook) + forbidden 0 + ANU key 0 + flag default-off + test cron 0 시 **MERGE_READY_CANDIDATE** 보고.
- **브랜치**: `task/task-2724-dev1`
- **워크트리 경로**: `/home/jay/.cokacdir/workspace/881B3CC7/wt-2724-dev1`
- **머지 의견**: bounded 1줄 가드 + fail-closed marker + 4 회귀. 23 passed, 회장 10 전부 PASS, forbidden 0. task-2724의 마지막 bounded robustness fix — 이후 새 HIGH/CRITICAL → CHAIR_REQUIRED(patch loop 금지).

## ANU normal callback cron 등록

- collector_role=ANU, 독립 ANU key 사용(sealed, 리터럴 노출 금지), schedule id=`FB214A33`, 실행 2026-06-03 12:50:17(+15m), `--once`.
- executor self-key 미사용 → SELF_COLLECTOR_FORBIDDEN 회피. sendfile-only 아님 → SENDFILE_ONLY/NOT_REGISTERED 회피. 봇 자가검증 미수행 → SELF_COLLECTOR_VERIFICATION_FORBIDDEN 회피.
