# task-2721+3 완료 보고 — PR #167 P0-b HIGH(result.json write race) fix

> 작성: 헤르메스(dev1 팀장) | 2026-06-02 | 회장 인가(Option 1, 새 HIGH 1건 명시 승인) 근거

## SCQA

**S**: PR #167 P0-b OS-level pickup 경로(`dispatch/anu_pickup_driver.py`)가 구현되어, systemd path watcher가 `memory/events/task-*.result.json` 생성을 감지하면 driver가 6조건 검증 후 ANU-owned wake를 빌드한다(active=false, default disabled).

**C**: path watcher가 writer의 flush 완료 **전**에 result.json 생성을 감지하면, driver가 부분(truncated) JSON을 즉시 quarantine로 이동시켜 **데이터 손실 + callback 누락**이 발생한다(fresh HIGH 1건, dismiss 금지 유효 결함).

**Q**: writer 미완(부분 write) 가능성을 먼저 배제하여, 정상 result.json을 손실 없이 pickup하면서 진짜 깨진 파일만 quarantine할 수 있는가?

**A**: driver에 **readiness grace window**를 도입했다. target 판정 직후 `_check_readiness()`로 (1) mtime이 `now - STABLE_SEC(2.0s)` 이내(최근 생성/수정)이거나 (2) size/mtime이 짧은 retry window(≤0.6s, 3회 stat) 내 불안정이거나 (3) stat 실패면 → 신규 verdict `NOOP_NOT_READY`(DEFER)로 처리하여 **wake/quarantine 모두 금지**, 다음 트리거에서 재평가한다. grace 통과(aged+stable) 후에만 기존 6조건을 적용하고, null byte(`\x00`) truncation 흔적은 grace 후에도 잔존 시 `null_byte` quarantine한다. regression 22→26 PASS, L1 스모크(실 프로세스) 통과, 실 wake/cron 발사 0.

## 수정 파일 (git diff origin/main = 6, expected_files 일치)
- `dispatch/anu_pickup_driver.py` ← 주 수정 (readiness grace window + NOOP_NOT_READY + null byte 방어)
- `tests/regression/test_anu_pickup_driver_2721.py` ← regression 22→26
- (이전 Phase 유지) `deploy/systemd/anu-pickup.path`, `deploy/systemd/anu-pickup.service`, `scripts/anu_pickup_entrypoint.sh`, `docs/p0b_driver_runbook_260601.md`
- `scripts/finish-task.sh` 수정 0건. P0-a main 무수정. ANU key literal net-new 0건.

## HIGH race fix 핵심 변경 내역
1. **부분 JSON 즉시 quarantine 금지**: target 후 readiness 게이트가 6조건보다 먼저 실행. writer flush 미완 가능성을 먼저 배제.
2. **readiness 판정** `_check_readiness(path, clock, stable_sec, retries, interval, sleep_fn, stat_fn)`:
   - mtime age = `clock().timestamp() - st_mtime`. `age < STABLE_SEC` → `recent_mtime` DEFER.
   - size/mtime 안정성: 최대 `STABILITY_RETRIES(3)`회 `interval(0.2s)` 간격 stat → 마지막 두 샘플 불변이어야 ready. 불변 시 조기 종료(무한 sleep 없음, window ≤ 약 0.6s).
   - stat 실패 → `stat_fail` DEFER.
   - 파라미터는 모듈 상수(`STABLE_SEC`, `STABILITY_RETRIES`, `STABILITY_INTERVAL_SEC`) + process_one/scan_once kwargs로 테스트 주입 가능.
3. **verdict enum 추가**: `VERDICT_NOOP_NOT_READY = "NOOP_NOT_READY"` — wake 0 / quarantine 0 / evidence 기록 / 다음 평가 재시도.
4. **null byte 방어**: parse 전 `b"\x00" in raw` → `null_byte` quarantine(grace 통과 후). grace 내 최근 mtime은 readiness에서 이미 DEFER.
5. 기존 6조건(size0/parse_fail/schema_fail/owner_proof/dedupe)은 readiness 통과 후 그대로 유지(회귀 0).

## 테스트 결과
- `python3 -m pytest tests/regression/test_anu_pickup_driver_2721.py -q` → **26 passed** (기존 22 + 신규 4).
- 신규: #19 부분JSON+최근mtime→NOOP_NOT_READY(파일보존/quarantine미생성/pickup미호출), #20 aged invalid→parse_fail quarantine, #21 null byte recent→DEFER / aged→null_byte quarantine, #22 `_check_readiness` 단위(recent/aged/stat_fail).
- pickup_once / verify_collector_authoritative 전부 mock. 실 wake/cron 발사 0. ANU key literal 0건.

## L1 스모크테스트 결과
- **서버 재시작**: 해당없음 (driver는 systemd path-activated 모듈, active=false 유지 — 서버 프로세스 아님).
- **API 응답 확인**: 해당없음 (HTTP API 아님). 대신 실 프로세스 모듈 실행으로 대체:
  - 실 Python 프로세스에서 `dispatch.anu_pickup_driver` 로드 후 임시 디렉토리로 3 시나리오 실행 —
    A) 부분JSON+최근mtime → `NOOP_NOT_READY`(파일보존, pickup 0), B) aged invalid → `QUARANTINE/parse_fail`(이동완료, pickup 0), C) aged 정상JSON → `WAKE_BUILT/ANU`(pickup mock 1회). 모두 통과.
- **스크린샷**: 해당없음 (CLI/모듈 작업).

## 검증 게이트
- `git diff --name-only origin/main` = 6파일(expected_files 일치). finish-task.sh 0건. ANU key net-new 0건.
- goal assertion: pytest PASS + `('NOT_READY' in s or 'DEFER' in s) and 'mtime' in s` → PASS.
- readiness(size/mtime 안정 + mtime grace) 로직 존재. NOT_READY/DEFER 분기 존재. 단순 무한 sleep 없음(유한 window).
- 금지사항 준수: writer atomic화 미적용(P0-b+1 유지), systemctl/설치/activation flag 생성 0, merge/push/PR 0, lock 재도입 0, active=false 유지.

## 발견 이슈 및 해결
### 자체 해결 (1건)
1. **기존 quarantine/wake 테스트가 fresh 파일을 써서 readiness 도입 후 DEFER로 깨짐** — 해당 테스트에 `_age()`(과거 mtime) 헬퍼 적용 + `sleep_fn=_NO_SLEEP` 주입으로 aged 상태 모사하여 기존 검증 의도 유지(회귀 0).
### 범위 외 미해결 (1건)
1. **result.json writer atomic화(tmp+rename)** — 범위 외 사유: 회장 verbatim "writer atomic = P0-b+1 후속 유지", finish-task.sh 수정 금지. readiness grace는 reader 측 방어로 충분.

## 모델 사용 기록
- 불칸(백엔드, driver 구현): **sonnet** — 로직 구현.
- 아르고스(테스터, regression): **sonnet** — 테스트 작성/검증.
- 헤르메스(팀장, opus): 설계/검토/통합/L1 스모크/보고. (haiku 미사용)

## 머지 판단
- **머지 필요**: No (팀장 머지 금지 — 회장 verdict). finalize 로컬 commit only.
- **브랜치**: `task/task-2721-dev1`
- **워크트리 경로**: `/home/jay/workspace/.worktrees/task-2721-dev1`
- **머지 의견**: ANU 독립 재검증(6파일/readiness DEFER/aged invalid quarantine/regression 26/key 0/active=false) → FF push → OWNER `/gemini review` 1회 → CI GREEN + fresh unresolved 0 → MERGE_READY_CANDIDATE 후보. 새 HIGH 또는 동일 blocker 반복 시 CHAIR_REQUIRED. merge는 회장 승인 전 금지.
