# OS_LEVEL_PICKUP_RUNNER_IMPLEMENTATION_SPEC_PACKET (구현 spec 설계 only — 구현/PR/설치/ACTIVE=true/queue 0)

작성: 2026-06-09 KST / ANU 직접 read-only / base CODE=/home/jay/p0b-pickup-main(origin/main #186/#187)
상태: `OS_LEVEL_PICKUP_RUNNER_IMPLEMENTATION_SPEC_PENDING_ACTIVE_FALSE`
근거: DECISION_PACKET 4결정 회장 확정(path+timer / collector_result schema / 2-tier relay / field+prompt now·hook followup) + 6 구현 전제.
주의: 회장 검토용 설계 spec. dispatch task 발행 시 D-SPEC-EXACTNESS(self-check→external lint(Codex)→spec-anchor) 별도 적용. 현재 "spec locked" 아님.

## ★ 핵심 변경 지점 (Surgical)
- 현 `process_one`: owner-proof(`verify_fn`)=VERDICT_AUTHORITATIVE → 곧바로 `launcher_fn`(wake) 직행(launcher_fn=None=surface only).
- **삽입**: owner-proof PASS ↔ wake **사이에 deterministic closeout 단계** 추가. closeout 이 `.done.acked`+`collector_result.json` 직접 완결 → **대부분 wake 0 종료**. `agent_relay.required=true` 예외만 2-tier relay.
- 기존 launcher_fn surface-only 보존(주입 없으면 실행 0) → ACTIVE=false 안전 불변.

## 1. expected_files
**신규**:
- `deploy/systemd/anu-pickup.timer` — path+timer 병행(결정1).
- `dispatch/anu_collector_result.py` — collector_result.json schema dataclass + atomic writer(결정2).
- `dispatch/anu_terminal_relay.py` — 2-tier relay(sendfile 기본 / cron 예외)(결정3).
- `tests/test_anu_collector_result.py`, `tests/test_anu_pickup_closeout.py`, `tests/test_anu_terminal_relay.py` — regression.
**수정(최소)**:
- `dispatch/anu_pickup_driver.py` — `process_one` 에 deterministic closeout + agent_relay 분기 결선(owner-proof↔wake 사이).
- dev bot dispatch prompt 템플릿(해당 prompt 소스) — `callback_schedule_created:false` 계약 명문(결정4 즉시분).
**재사용(수정 0)**:
- `dispatch/anu_owned_callback_enforcement.py`(owner-proof resolve_authoritative_owner/verify_collector_authoritative).
- `dispatch/anu_result_pickup_runner.py`(dedupe ledger/done 패턴).
- `scripts/anu_pickup_entrypoint.sh`(timer 도 동일 entrypoint 호출 → 수정 0), `deploy/systemd/anu-pickup.path`/`.service`(수정 0).
**후속(이 spec 비포함·설계만 9)**:
- PreToolUse hook(executor cokacdir --cron 차단) — 별도 task.
- `forbidden_paths`: dispatch/anu_owned_callback_enforcement.py 의 owner-proof 핵심 로직, .env.keys, scripts/harness/*, canonical reset 경로.

## 2. path+timer unit 설계
- `anu-pickup.path`(기존, 수정 0): PathExistsGlob → 즉시 트리거.
- `anu-pickup.timer`(신규): `OnBootSec=2min` + `OnUnitActiveSec=120s`, `Unit=anu-pickup.service`, `[Install] WantedBy=timers.target`. = missed event/boot/daemon gap 보완.
- 둘 다 `anu-pickup.service`(동일 oneshot) 호출 → 같은 entrypoint → 같은 driver. **중복은 idempotency(7)가 흡수.**
- 설치는 production activation 단계(13)에서만. 현재 미설치 유지.

## 3. collector_result.json schema (`dispatch/anu_collector_result.py`)
- 위치: `memory/events/task-<id>.collector_result.json`(원본 result.json 과 1:1 분리). atomic write(os.replace).
- 필드(고정):
  - `task_id`: str
  - `source_result_sha256`: str
  - `verdict`: str(driver 9 verdict 중)
  - `owner_proof`: `{outcome: ANU_OWNED|NOT_ANU_OWNED_OR_ACCESS_DENIED|QUERY_FAILED|PENDING_OWNER_PROOF, schedule_id: str|null, query_ok: bool}`
  - `dedupe`: `{hit: bool, ledger_key: "<task_id>:<sha256>"}`
  - `scope`: `{pass: bool, detail: str}`
  - `closeout_action`: `none|done_acked|quarantined|deferred`
  - `agent_relay`: `{required: bool, reason: gemini_finding|merge_ready_ambiguous|critical7|quarantine_reason|consolidated_report|null}`
  - `ts_kst`: str, `runner_version`: str
- **raw key 0**: owner_key/key-derived/argv literal 금지(sha·argv_len 만). prompt 본문 0.

## 4. deterministic verdict 처리표
| verdict | 조건 | closeout_action | collector_result | wake/relay |
|---|---|---|---|---|
| NOOP_DISABLED | flag OFF | none | 미작성 | 0 |
| NOOP_NOT_TARGET | task-*.result.json final 아님 | none | 미작성 | 0 |
| NOOP_LEGACY_SKIP | mtime<epoch | none | 미작성 | 0 |
| NOOP_NOT_READY | readiness 미충족 | deferred | 작성(deferred) | 0(다음 cycle) |
| NOOP_MAX_FILES_DEFER | MAX_FILES=50 초과 | deferred | 미작성 | 0 |
| QUARANTINE | schema 위반/owner-proof FAIL | quarantined | 작성(quarantined) | relay(예외, 5) |
| PICKUP_SKIP | .pickup.done/.done.acked 존재 or dedupe | none | 미작성(이미 처리) | 0 |
| **CLOSEOUT_DONE**(신규) | owner-proof ANU + scope PASS + agent_relay 불요 | done_acked | 작성(done_acked) | **0(결정론 완결)** |
| WAKE_BUILT→RELAY | agent_relay.required=true | done_acked or quarantined | 작성 | relay 1회 |
- ★ 신규 verdict `CLOSEOUT_DONE` = green-path 결정론 종료(현 WAKE_BUILT 대체 주경로). wake 0.

## 5. agent_relay.required 분기표
| reason | 트리거 | relay tier |
|---|---|---|
| (none) | green closeout(owner-proof ANU·scope PASS·단순 .done) | relay 0(sendfile 보고만 선택적) |
| quarantine_reason | owner-proof NOT_ANU/QUERY_FAILED | 예외 relay(ANU) |
| gemini_finding | result 에 Gemini finding 표식 | 예외 relay(ANU) |
| merge_ready_ambiguous | merge-ready 판정 모호 | 예외 relay(ANU) |
| critical7 | Critical7 표식 | 예외 relay(ANU) |
| consolidated_report | 다수 PR 종합 필요 | 예외 relay(ANU) |
- 기본 경로(none): runner 완결 → **sendfile 보고만**(coupling-free). 예외만 cron ANU relay(merge/dispatch/PR 금지).

## 6. owner-proof 4-outcome 처리
- `resolve_authoritative_owner(envelope.schedule_id, RealCokacdirCronHistoryProbe())` = `cokacdir --cron-history <sid> --key <ANU sealed>` **read-only**.
- | outcome | 처리 |
  |---|---|
  | OWNER_ANU | 통과 → deterministic closeout |
  | OWNER_NOT_ANU | **fail-closed** → QUARANTINE + agent_relay(quarantine_reason) |
  | OWNER_QUERY_FAILED | **fail-closed** → QUARANTINE |
  | OWNER_PENDING | **retry 보류**(다음 cycle, false quarantine 차단) → NOOP_NOT_READY(deferred) |
- `self_key_used:true` envelope → 즉시 거부(self-collector). stale envelope → staleness gate REJECT.
- ★ read 전용 → cron-fire 의존 0 → inbound-coupling 무관.

## 7. dedupe / .done.acked / flock idempotency
- **3중**(전제: 결정 idempotent 1회):
  - dedupe ledger((task_id, source_result_sha256)) — pickup_once 패턴 재사용. closeout 기록 시 ledger append.
  - `.done.acked` marker — closeout 완결 표식(`memory/events/<task_id>.done` → `.done.acked` rename, 기존 .done 프로토콜 정합). + `<task_id>.pickup.done` 내부 마커.
  - flock single-flight(entrypoint, 기존) — 동시 실행 0.
- path 재발화/timer 중복/재부팅 → ledger+marker 로 SKIP. **real closeout 1회.**

## 8. callback_schedule_created:false enforcement (결정4 즉시분)
- result.json schema 에 `callback_schedule_created: false` 필드 **필수화**. dev bot = 이 필드 false 로 작성(봇이 cokacdir --cron callback 미등록 선언).
- runner 검증: 필드 부재 or true → **계약 위반 기록**(collector_result 에 `contract_violation: callback_schedule_created`) + agent_relay(quarantine_reason 또는 별도 경고). 단 즉시분은 **관측·기록**(차단은 후속 hook).
- dispatch prompt 명문: "result.json 작성만, cokacdir --cron callback schedule 등록 0, callback_schedule_created:false 명시".

## 9. executor cokacdir --cron hook 차단 후속 설계 (이 spec 비구현)
- PreToolUse hook(dev bot 세션 settings): Bash tool 호출 중 `cokacdir ... --cron` 패턴 매칭 → **deny**(self-collector 근원 실차단).
- 결선: dev bot 세션 launch 시 settings.json 의 PreToolUse hook 등록. 작동 실패 시(hook 미적용) dispatch 거부 fail-closed.
- ★ 난이도: dev bot 세션 settings 결선 + hook 패턴 정확도(false positive 회피). → **별도 task**. 이 spec 은 설계만, 구현 0.

## 10. legacy queue 0 보장
- `_legacy_cutoff_check`: result mtime < activation_epoch → NOOP_LEGACY_SKIP(move 0/wake 0/quarantine 0/collector_result 미작성). 기존 events 128 무변동.
- scan_once paths = 단일 path 이벤트 대상(canonical 광역 스캔 0). production queue 미터치.

## 11. activation_epoch fail-closed
- epoch 마커(`memory/state/p0b_activation_epoch`) **부재 → skip("epoch_absent")**(fail-open 절대 금지 — epoch 없으면 전 result legacy 취급해 처리 0). 회장 전제4 정합.
- epoch 설정은 production activation(13) 단계에서만. 현재 ABSENT 유지.

## 12. dry-run / isolated test 계획
- **dry-run**: driver `process_one(dry_run=True)` → 실 .done.acked rename 0·실 relay 0·collector_result 는 isolated temp 에만. owner-proof/dedupe/scope/agent_relay 분기 경로만 검증.
- **isolated test**(tests/): tmp workspace(canonical 미접촉) + fake cron-history probe(OWNER_ANU/NOT_ANU/QUERY_FAILED/PENDING 4 fixture) + fake result.json(완전/schema 위반/legacy/dedupe/self_key) → 각 verdict·closeout_action·collector_result·agent_relay 분기 단위 검증. **canonical events 0 touch.**
- green-path agent wake 0 실증(launcher 호출 0 assert).

## 13. production activation 전 gate
1. 12 regression/dry-run GREEN(각 verdict·owner-proof 4·agent_relay 분기·green-path wake 0).
2. owner-proof read 가 quiet window 에서 성공(coupling 무관) 실증.
3. dev bot 계약(callback_schedule_created:false) 관측 검증.
4. relay 경계(merge/dispatch/PR 실행 0) 검증.
5. legacy 128 NOOP 재확인.
6. (회장 승인) systemd path+timer 설치 + flag enabled + activation_epoch 설정 + **통제 1회** 실감지 canary(단일 result, ack≤1, write≤1).
7. real activation(ACTIVE) = 회장 별도 승인.

## 14. rollback 계획
- flag OFF(`p0b_driver_enabled`≠enabled) → 전면 no-op 즉시 복귀(entrypoint 게이트).
- systemd: `systemctl --user disable --now anu-pickup.path anu-pickup.timer` → 트리거 0.
- 코드: PR revert(driver process_one 변경·신규 파일 제거). collector_result.json 은 추가 산출물이라 기존 result.json/events 무영향.
- activation_epoch 마커 제거 → 전 result legacy 취급(fail-closed) → 처리 0.
- ledger/.done.acked 는 idempotent 라 롤백 후 재처리해도 SKIP. **canonical HEAD/events 원상 복귀 가능.**

## capability matrix
- spec_scope = IMPLEMENTATION_SPEC_DESIGN(미구현).
- core_change = PROCESS_ONE_DETERMINISTIC_CLOSEOUT_INSERT.
- expected_files = 4신규+2수정+3재사용+1후속설계.
- ACTIVE=false / production_activation_gate = HARD BLOCK.

## 금지 (현재)
구현 / PR / systemd 설치 / ACTIVE=true / production queue 처리 / daemon restart / direct Claude launch / 추가 canary. **spec 설계 packet 까지만.**

## 판정
- **OS_LEVEL_PICKUP_RUNNER_IMPLEMENTATION_SPEC_PACKET_READY**. 핵심: process_one 의 owner-proof↔wake 사이에 deterministic closeout(CLOSEOUT_DONE verdict) 삽입 → green-path wake 0, 예외만 2-tier relay. expected_files 최소(4신규/2수정/3재사용), owner-proof read-only 재사용, 3중 idempotency, legacy/epoch fail-closed, dry-run isolated, rollback flag/systemd/revert. 구현/PR/설치/canary = 회장 별도 승인 + dispatch 시 D-SPEC-EXACTNESS external lint.
