# task-2730 — OS-level pickup deterministic closeout 구현 보고

- 작성: 2026-06-09 KST / dev1 헤르메스 (executor)
- 상태: **구현 완료 · regression GREEN · Codex 재lint HIGH/CRITICAL=0 · PR #198 open · merge/activation = 회장 별도 승인 대기**
- PR: https://github.com/Jeon-Jonghyuk/dev_workspace/pull/198 (branch `task/task-2730-dev1-os-pickup-closeout`, base `main`@`51b3a420`/#197)
- spec-anchor: `os_level_pickup_runner_design_lock_candidate_packet_260610`(sha256 57194abc…) + `..._corrected_spec_v2_packet_260609`(sha256 876ee683…) — 무결성 확인 후 그대로 수행.

## 1. 구현 요약
green `result.json` 을 OS-level pickup runner 가 **결정론 closeout(`CLOSEOUT_DONE`, wake 0)** 하고,
`agent_relay.required` 예외만 2-tier terminal relay 로 분기한다.

### 보존 anchor (불변 — 코드로 확인)
- **ANCHOR-A**: closeout write OWNER = `pickup_once`(+ helper `anu_collector_result.write_collector_result`).
  `process_one`(driver)는 closeout 미소유 — owner-proof 라벨 전달 + verdict 분기만.
- **ANCHOR-B**: durability order = **ledger(dedupe·fsync) → collector_result(atomic os.replace + parent-dir fsync) → done marker(terminal sentinel·마지막, parent-dir fsync)**.

## 2. expected_files (신규 4 + 수정 3 + 테스트)
- 신규 `deploy/systemd/anu-pickup.timer` — OnBootSec/OnUnitActiveSec=120s, 같은 service. **파일 작성만(미설치)**.
- 신규 `dispatch/anu_collector_result.py` — schema dataclass + atomic writer + `_fsync_dir` + `determine_agent_relay`(relay_hints 4필드만·추론 0). **raw key 0**.
- 신규 `dispatch/anu_terminal_relay.py` — allowlist-only static(shell=False·git/gh/dispatch/merge import 0·subcommand 화이트리스트 raise).
- 수정 `dispatch/anu_result_pickup_runner.py` — closeout 통합(§8 ledger 직후↔§9 marker 직전) + `CLOSEOUT_DONE` verdict + crash recovery.
- 수정 `dispatch/anu_pickup_driver.py` — `VERDICT_CLOSEOUT_DONE`(launcher 미호출+move) + relay 분기(relay_fn) + marker-confirmed move.
- 수정 `prompts/team_prompts.py` — `relay_hints` + `callback_schedule_created:false` producer contract(append-only, 기존 doctrine 토큰 보존).
- 테스트: 신규 `tests/test_anu_pickup_closeout.py`(29).

## 3. regression (GREEN, 111 tests)
- 신규 `test_anu_pickup_closeout.py` 29: owner L1/L2 · relay_hints 4종 분기 · durability order + crash 3케이스 · idempotency · terminal_relay static(import/argv/shell) · green launcher 0 · raw key 0 · driver crash recovery · dry-run isolated(canonical 0 touch).
- 기존 82 GREEN: `test_anu_result_pickup_runner_2720`(12) · `test_anu_pickup_driver_2721`(34) · `test_anu_pickup_real_wake_wiring_2729p8`(25) · `test_anu_pickup_activation_hardening_2729p7`(11).

## 4. Codex 재lint (HIGH/CRITICAL = 0)
3 pass 수행. 초기 5 HIGH → 전부 처리:
1. (HIGH) driver dedupe gate 가 crash recovery 차단 + sha256 무시 → **fix**: condition-6 의 `_dedupe_hit` 조기단락 폐기, ledger dedupe+recovery 를 `pickup_once`(sha256-aware authoritative)로 일원화. terminal marker(done/acked)만 조기단락.
2. (HIGH) marker write 실패 시 move 되어 retry 경로 소실 → **fix**: driver 가 `_marker_confirmed` 시에만 move(미확정 시 이동 보류·다음 cycle recovery).
3. (HIGH) collector_result rename non-durable → **fix**: `write_collector_result` parent-dir fsync.
4. (HIGH) marker rename non-durable → **fix**: marker os.replace 후 result_dir fsync.
5. (HIGH) `ANU_NORMAL_COLLECTOR_KEY` 하드코딩 → **pre-existing/out-of-scope**: task-2730 추가분(append-only relay 블록)에 key 0. 기존 doctrine + injection 테스트가 강제하는 기존 코드 — 미변경(문서화).
- pass #2 추가 HIGH: recovery 가 sealed-key 가용성에 묶여 stranding → **fix**: sealed-key 게이트를 `not already_in_ledger` 일 때만 적용(recovery 면제·fresh 는 유지). → pass #3 **PASS(0 HIGH/CRITICAL)**.

## 5. 안전 (회장 승인 전 금지 준수)
- ACTIVE=false: `systemctl enable/start` 0 · p0b flag enable 0 · `activation_epoch` 0 · production events(legacy) 무변동 · canary 0.
- dry-run/test = isolated temp(canonical 0 touch). green CLOSEOUT_DONE 도 scan_once flag(`p0b_driver_enabled`) OFF 면 미진입(테스트로 확인).
- **merge·activation(systemd 설치·flag·epoch) = 회장 별도 승인 전 금지.**

## 6. 투명성 — 기존 regression 테스트 reconcile (필수 부수)
green→`CLOSEOUT_DONE` 계약 변경(task 본질)으로 runner 직접 검증 테스트 2개를 의도 보존 최소 수정:
- `test_anu_result_pickup_runner_2720.py`: collector_result helper 선로드 + wake/argv 검증 fixture 에 `relay_hints` 부여(wake path = relay path 로 매핑). 모든 assertion 의도 보존.
- `test_anu_pickup_activation_hardening_2729p7.py::test_marker_write_failed_wake_kept`: 신 durability order 반영(전역 os.replace 실패 → 첫 atomic write=collector_result → `COLLECTOR_WRITE_FAILED` retry-safe → recovery SKIP_DEDUPE). marker-only 실패(closeout 유지)는 신규 파일에서 검증.
- `2729p8`/`2721` 무변경(driver 변경은 기존 동작 보존 — launcher_fn=None 기본·error 문자열 호환).

## 7. PR / CI handoff
- PR #198 = 단일 clean commit(내 9파일만, +1430/-101). ※ 워크트리 auto-micro-commit 데몬이 무관 canonical dirty(memory/backups·utils/replacement_pr_runner)를 branch 에 섞어 commit 했으나, PR 직전 origin/main 복원 후 의도 파일만 재커밋하여 scope 격리.
- CI_WATCH_HANDOFF: `memory/events/task-2730.ci-watch-handoff.json`(pr_number=198) 생성 — 직접 CI 장기 대기 0, watcher 위임. CI/Gemini gate 는 watcher 가 확인.
- same-PR post-Gemini push 0 · bot `/gemini review` 작성 0 · long polling 0.
