# task-2720+4 — PR #166 P0-a 4차 micro-fix(예외 승인): done_path atomic write

## 회장 인가 (2026-06-01, Option A — 4차 fix 예외 승인)
`anu_result_pickup_runner.py` L325 `done_path` 비원자 write = **유효 결함**. `open(done_path,'w')` 직접 기록은 크래시/중단 시 빈/절단 마커를 남기고, 이후 `pickup_once`가 L186 `os.path.exists(done_path)`만 보고 **silent skip** → P0-a callback 미발사. dismiss 금지, **단일 micro-fix 승인**.
★ **이번이 4차 예외**. atomic write 패치 후 새 High 또 나오면 추가 fix 금지 → LOOP_BOUNDARY 재보고.

## worktree
- `/home/jay/workspace/.worktrees/task-2720-dev1` (branch `task/task-2720-dev1`, head `b19049fa`)에서 **이어서**. base origin/main.

## expected_files (정확히 1개 — 그 외 어떤 파일도 수정 금지)
1. `dispatch/anu_result_pickup_runner.py`  ← **단일 수정**
(evidence: memory/events/task-2720+4.done · memory/reports/task-2720.md 갱신 허용)

## 수정 — done_path 기록부를 atomic write 로만 (회장 권장 방식 verbatim)
대상: L323~328 `done_path` 기록부(`with open(done_path,"w") ...`).
1. `done_path` 와 **같은 디렉터리**에 임시 파일 생성(예: `f"{done_path}.tmp-{os.getpid()}"` 또는 tempfile.NamedTemporaryFile(dir=result_dir, delete=False)).
2. 임시 파일에 기존 `done_content`(marker JSON) 작성.
3. `flush()` + `os.fsync(fileno())` 적용(L310~311 ledger 패턴과 동일).
4. `os.replace(tmp_path, done_path)` 로 **원자 교체**.
5. 실패(OSError) 시 tmp 파일 **best-effort cleanup**(`os.unlink` try/except) 후 기존처럼 `pass`(done_path_out=None 유지).
- `done_content` 내용/스키마/필드 변경 0. `done_path_out` 반환 의미 변경 0.

## 금지 (회장 verbatim 인가범위 4·5·6)
- lock/`pickup.lock`/`O_EXCL`/`_lock_acquired`/`STALE_LOCK` 등 **파일락 로직 재도입 금지**.
- callback launcher / schedule 등록 / ANU key / owner_key / result pickup contract / dedupe / terminal marker **의미 변경 금지**.
- expected_files **밖 수정 금지**. merge/force push/rebase/admin override/OS-level 설치/PR #163 close 금지.

## regression (회장 인가범위 7)
- 기존 `test_anu_result_pickup_runner_2720.py` + `test_anu_owned_callback_enforcement_2717.py` = **32 PASS 필수**(수정/추가 시 증가만, 회귀 0).
- done marker atomic 동작 입증(가능하면): 정상 시 done_path 존재+내용 일치 / tmp 파일 잔존 0.

## 검증 (finalize 전)
- `python3 -m pytest tests/regression/test_anu_result_pickup_runner_2720.py tests/regression/test_anu_owned_callback_enforcement_2717.py -q` 전 PASS.
- `git diff --name-only origin/main` = `dispatch/anu_result_pickup_runner.py` **1개**(+evidence). ANU key literal net-new **0**(인가범위 8). active 완료주장 0.
- lock 심볼 grep 0 유지: `grep -cE 'pickup.lock|_lock_acquired|_STALE_LOCK|O_EXCL' dispatch/anu_result_pickup_runner.py` = **0**.
- `os.replace` 1건 추가 확인. `with open(done_path, "w")` 직접 기록 **0**.

## finalize (로컬 commit 까지만)
1. fix → regression PASS → `git add dispatch/anu_result_pickup_runner.py` → 로컬 commit. **push/PR/merge 금지(ANU 수행)**.
2. `memory/reports/task-2720.md` 에 4차 처리내역(done_path atomic write / os.replace / tmp cleanup) 추가.
3. `memory/events/task-2720+4.done` 생성. ANU key(c119085, sealed) callback. ★ ANU key literal 노출 0.

## ANU 후속 (봇 아님 — governance, 회장 인가범위 9·10·11)
ANU 독립 재검증 → FF push → OWNER `/gemini review` 1회 →
fresh head CI 11/11 + unresolved 0 + MERGEABLE + diff 1코드+evidence + ANU key net-new 0 + active 0 →
**MERGE_READY_CANDIDATE** 보고 / 새 High 시 **LOOP_BOUNDARY**. merge 회장 승인 전 금지.

## allowed_resources
```yaml
allowed_resources:
  paths:
    - "dispatch/anu_result_pickup_runner.py"
    - "memory/events/task-2720+4.done"
    - "memory/reports/task-2720.md"
  forbidden_paths:
    - ".github/**"
    - "dispatch/anu_owned_callback_enforcement.py"
    - "scripts/finish-task.sh"
    - "anu_v2/ci_gemini_watcher_runner.py"
  commands:
    - "pytest"
    - "python3 -m pytest"
    - "python3 -m py_compile"
    - "gh"
  merge_policy: "none"
  ttl_hours: 48
```

## ★ dispatch 템플릿 G3(PR/머지) 무시. finalize 로컬 commit only 우선. finish-task --action pr 금지.

## goal_assertions (auto-generated)
- `python3 -m pytest tests/regression/test_anu_result_pickup_runner_2720.py tests/regression/test_anu_owned_callback_enforcement_2717.py -q`
- `python3 -c "import re; s=open('dispatch/anu_result_pickup_runner.py').read(); import sys; sys.exit(0 if ('os.replace' in s and re.search(r'open\(\s*done_path\s*,\s*[\"\x27]w', s) is None) else 1)"`