# task-2731 — finish-callback 강제 결선 (executor 완료 callback 코드강제 · dispatch 승인됨)

상태: `TASK2731_FINISH_CALLBACK_ENFORCEMENT_DISPATCH_APPROVED_ACTIVE_FALSE`
작성: 2026-06-10 KST / ANU / base CODE=/home/jay/p0b-pickup-main (main 95941e4e, task-2730 반영)
executor: dev6 페룬 / serial_only=true
설계 단일출처: `memory/plans/p0b-pickup/finish_callback_enforcement_followup_task_packet_260610.md` + `task2731_finish_callback_enforcement_prep_packet_260610.md`

## 0. SPEC-ANCHOR (보존 anchor, 축약/삭제 금지)
- ANCHOR-1: **executor 가 callback 안 쏘고 죽어도 OS runner/collector 가 result.json 을 수거해 완료 상태 생성 + ANU-owned callback launch 를 코드가 강제**(executor 세션 생존 무관·dev bot prompt 의존 0·finish-task 끝단 단독 의존 0).
- ANCHOR-2: callback = **ANU key 강제, self-key callback 거부**, launch audit jsonl 기록.
- ANCHOR-3: durability = ledger→collector_result→pickup.done(terminal sentinel), result 1건당 callback 1회 idempotent.

## 1. 목표
- dev1/dev2 closeout 미완(callback 미발사) 구조 결함 해소. executor 완료 신호 = **result.json 존재**. callback 책임 executor → OS runner/collector 경로 이관, **코드 강제**.

## 2. base / worktree
- base: main 95941e4e (CODE_ROOT=/home/jay/p0b-pickup-main).
- ★ worktree: **반드시 `.worktrees/task-2731-dev6` 형태로 명시 분기**(CODE_ROOT 직접 체크아웃 금지 — dev2 마찰 재발 방지). branch `task/task-2731-dev6`.

## 3. expected_files (좁힘 — 핵심=실행 경로 강제, prompt 수정 아님)
- **신규**: `dispatch/anu_callback_launch_audit.py`(callback 코드강제+audit jsonl·self-key 거부·idempotent) · `dispatch/anu_finish_preflight.py`(worktree path mismatch 탐지 + branch regex `-rN`/`+N` 안전 추출 + task-lock 마찰 우회 판정) · `tests/test_anu_finish_callback_enforcement.py`(fixture/regression).
- **수정**: `dispatch/anu_result_pickup_runner.py`(collector closeout → callback launch 결선) · `dispatch/anu_pickup_driver.py`(callback audit 호출) · result-json contract `terminal_intent` 필드(runner/driver 내 schema, **devbot prompt 수정 0**).
- **systemd unit(파일/fixture/dry-run 검증만, install 0)**: `deploy/systemd/anu-pickup.timer`/`.path`(필요 시 파일 작성·dry-run harness 검증).
- **재사용·수정0**: owner-proof(`dispatch/anu_owned_callback_enforcement.py`) — **credential/key 범위 확대 금지**.
- **보고서**: `memory/reports/task-2731.md`.
- **제외(expected_files 밖)**: devbot dispatch prompt · activation config · production queue · canonical dirty cleanup 관련 파일.
- ★ guard/branch regex 관련 수정이 scripts/ 에 필요하면 `anu_finish_preflight.py` 로 분리(신규 모듈)하여 expected_files 내부 유지. 기존 guard 직접 수정은 최소화·task-2731 관련만.

## 4. acceptance criteria (완료조건)
1. expected_files **내부 diff만**(밖 0).
2. fixture 기반 **regression GREEN**(isolated·canonical 0 touch).
3. **self-key callback 거부** 검증.
4. **ANU-owned callback launch audit jsonl 생성** 검증(task_id·owner_key_class·launch_ts·schedule_id·success·executor_session_independent).
5. result.json / collector_result / terminal marker / pickup.done **idempotency** 검증(1건 callback 1회).
6. **branch `-rN` / worktree mismatch / task-lock 마찰 재현 테스트 통과**.
7. systemd **install/enable/start 0**(파일/dry-run만).
8. ACTIVE=false 유지(flag OFF no-op).
9. PR 생성 후 CI/Gemini gate 확인.
10. merge = 회장 별도 승인 전 금지.
+ executor callback 미발사여도 runner/collector 가 완료 수거(ANCHOR-1) · owner-proof read-only 유지 · dev1/dev2 fixture 재현.

## 5. regression fixture (dev1/dev2 재현 필수)
- fixture-dev1: result.json+report 존재·`.done`/callback 미생성 → runner 수거·callback.
- fixture-dev2: result.json·worktree path mismatch·branch `-r2`·task-lock 마찰 → finish 미완 → result.json 만으로 수거·callback.
- fixture-callback-absent / fixture-self-key(거부) / fixture-branch-rN / fixture-worktree-mismatch / fixture-active-false(no-op) / fixture-idempotent(2회→1회).

## 6. forbidden actions
ACTIVE=true · systemd install/enable/start · activation_epoch 생성 · production queue 처리 · 사후 callback 임의 발사 · task-2730 재오픈 · PR #198/#199 조작 · canonical dirty cleanup · **credential/key 범위 확대** · **devbot prompt 수정** · expected_files 밖 수정 · merge · same-branch 오염 · **finish-task 우회 성공을 완료로 과장** · dev bot callback 자가생성 · canonical reset/clean/stash -u/checkout -f · owner-proof 핵심 변경 · spec 축약 · manual .done · same-PR post-Gemini push.

## 7. implementation plan (코드 경로 중심)
1. result-json contract: `terminal_intent`+`callback_schedule_created:false` schema enforce(runner/driver 코드, prompt 의존 제거).
2. OS runner detection(설계/dry-run): systemd path/timer unit 파일 + dry-run harness(install 0).
3. collector closeout 결선(anu_result_pickup_runner): owner-proof PASS → closeout → callback launch 호출.
4. callback launch audit(anu_callback_launch_audit.py): ANU key 강제·self-key 거부·audit jsonl·idempotent dedupe·executor_session_independent.
5. preflight(anu_finish_preflight.py): worktree mismatch 탐지 + branch `-rN/+N` 안전 추출 + task-lock 마찰 우회(result.json 수거 lock 독립).
6. regression(isolated) §5 fixture 전부 + idempotency + self-key 거부 + audit + ACTIVE=false no-op.
7. dry-run harness: systemd 미설치·flag OFF·isolated temp(canonical 0 touch).
8. **Codex 재lint(또는 동등 독립 정적 검토) HIGH/CRITICAL=0**.
9. PR 생성 전 **final diff 확인**(expected_files 내부).

## 8. finish/closeout (★ dev1/dev2 재발 방지)
- finish/closeout 끝까지 완료: result.json + `memory/reports/task-2731.md` + finish-task 정식 .done + **ANU key callback** 전부 생성 후 종료.
- 완료 시 ANU key `c119085addb0f8b7` normal completion callback cron(executor self-key 자가발사 금지). envelope=task_id·pr_number·result_path·report_path·sha256·collector_role=ANU·summary. ≤3900 bytes(wc -c).
- ★ 본 task 의 목표 자체가 callback 강제이나, **이 task 구현 중에는 기존 수동 callback 경로 사용**(자기 구현물로 자기 callback 발사 금지 — circular). serial_only.

## 판정
- task-2731 dispatch 승인. base main 95941e4e·worktree .worktrees/task-2731-dev6·expected_files 좁힘(prompt/activation/production/canonical 제외·key 확대 금지·systemd 파일/dry-run만). acceptance 10+·fixture 8·serial_only=true. merge/activation=회장 승인 전 금지.
