# task-2720 — P0-a: callback argv-only 제거 + result.json pickup runner 결선 (fresh main base)

## 회장 인가 (2026-05-31)
P0(callback wake 결선) 1단계. callback "argv-only 중간상태"(result.json + callback cron argv 생성 + 실발사 0)를 **result.json-only 완료 + ANU-owned pickup runner**로 치환한다. **OS-level driver 설치는 P0-b(별도 승인) — 본 task 금지.** P0-a 는 **wired 후보까지만**, active 주장 금지.
선행: P0-0 검증 = B(NEEDS_FRESH_P0A_PR). PR #163 통째 머지 부적절(stale base 2321dd8b + Gemini unresolved 21) → 모듈 2파일만 fresh main 이식.

## base (필수)
- **origin/main `5e714887`** fresh worktree. PR #163/#164/#165 worktree 와 격리. PR #163 **머지/close 금지**(보존).

## expected_files (정확히 5개 — 그 외 수정 금지)
1. `dispatch/anu_owned_callback_enforcement.py` — **PR#163 head `b257f2aa` 에서 이식** (`git show b257f2aa:dispatch/anu_owned_callback_enforcement.py` 추출, 신규 로직 0)
2. `tests/regression/test_anu_owned_callback_enforcement_2717.py` — 동 이식
3. `dispatch/anu_result_pickup_runner.py` — **신규** (pickup runner entrypoint)
4. `tests/regression/test_anu_result_pickup_runner_2720.py` — **신규** (regression, mock/fixture)
5. `scripts/finish-task.sh` — **L1585~1616 surgical 치환만** (argv-only callback launch 제거 → result.json-only). 그 외 라인 무수정.

## 구현 1 — 빌딩블록 이식 (파일 1·2)
- `git show b257f2aa:<path>` 로 모듈+test 추출해 fresh worktree 배치. **내용 수정 0**(이식만).
- 핵심 함수 보존: `executor_write_result_json`(result.json, schedule_created=False, signal RESULT_JSON_WRITTEN) / `anu_runner_pickup_and_fire`(ANU key pickup→wake argv, executor self-key REFUSE) / `verify_collector_authoritative`(SELF_COLLECTOR_QUARANTINE / PENDING_OWNER_PROOF).
- `FIRE_NOT_ACTIVATED` 기본·`fire_callback_request(activate=True)→PHASE2_REQUIRED`(실발사 0) 유지.
- ANU key literal 노출 0 확인(import 상수만).

## 구현 2 — pickup runner (파일 3·4)
- `dispatch/anu_result_pickup_runner.py`:
  - `pickup_once(result_json_path, *, gh_probe=None, clock=None, sealed_key_loader=None) -> PickupResult`
    1. result.json 읽기·파싱(없으면 NO_RESULT_JSON, 손상 FAIL)
    2. **lock**: `<task>.pickup.lock` atomic create(O_CREAT|O_EXCL). 경합 시 backoff/no-op.
    3. **terminal-marker no-op**: `<task>.pickup.done`/`.acked` 존재 시 SKIP(이미 처리).
    4. **dedupe**: (task_id + sha256) 4-tuple — 기존 `callback_4tuple_index.jsonl` 재사용. 중복이면 wake 0.
    5. `verify_collector_authoritative` → PASS(ANU 소유)만 진행. QUARANTINE/PENDING 은 보존+wake 0.
    6. **sealed-key proof**: ANU key 는 `utils.env_loader.load_env_keys()`(.env.keys) 로만 로드(`sealed_key_loader` 주입). argv/prompt/literal 노출 0. 미로드 시 fail-closed.
    7. `anu_runner_pickup_and_fire(anu_key=<sealed>)` → wake request(argv) 생성. executor self-key 면 REFUSE.
    8. 처리 후 `<task>.pickup.done` marker 작성(idempotent).
  - **OS-level 설치/감시 driver 0** — pickup_once 는 함수(P0-b driver 가 호출). 본 task 는 함수+테스트까지.
  - 부수효과(gh/clock/key/fs)는 주입형 — 테스트 mock.

## 구현 3 — finish-task.sh 치환 (파일 5)
- L1585~1616 (`T2626_ANU_KEY` 하드코딩 + `callback_kind=normal` envelope + `normal_fallback_callback_helper launch ... > callback-launch.json`) 블록을 **executor result.json 작성 호출로 치환**:
  - executor 는 `python3 -m dispatch.anu_owned_callback_enforcement` 경로 또는 helper 로 **result.json(memory/events/${TASK_ID}.result.json) 작성만**. callback cron argv 생성 0.
  - ★ **`T2626_ANU_KEY="c119085..."` 하드코딩 제거** — executor 경로에 ANU key 불필요(pickup runner 만 sealed 로드). v2.10 #3 준수.
  - 기존 게이트(SENDFILE_ONLY/NOT_REGISTERED 의미)는 **result.json 미작성 시 fail-closed** 로 의미 이전.
  - cron 타이밍 코드(`T2661_NORMAL_ABSOLUTE_AT` 등)는 normal 경로에서 제거(fallback dead-man 영향 0 — 해당 라인은 normal 전용).

## 회장 보고 필수 (Critical7)
credential/key expansion · forbidden path 수정 · SELF_COLLECTOR 확산(다수 task) · admin override · result.json secret 노출 · OS-level 설치 권한 요구 · pickup runner 가 ANU key 외 권한 요구.

## 자동 수렴 (non-Critical, 보고 없이)
result.json schema minor · pickup transient retry · 정상 self-collector 1건 격리 · lock 경합 backoff · dedupe no-op.

## regression (전부 mock/fixture, 네트워크 0)
- `fx_result_json_valid` → pickup → ANU wake argv(ANU key) 생성
- `fx_self_collector_key` → SELF_COLLECTOR_QUARANTINE, wake 0
- `fx_owner_proof_pending` → PENDING_OWNER_PROOF, retryable
- `fx_duplicate_result_json` → idempotent, wake 1회만(2회차 no-op via dedupe/marker)
- `fx_terminal_marker_present` → no-op
- `fx_invalid_task_id` → reject(빈/형식오류)
- `fx_finish_task_emits_result_json_only` → callback cron argv 생성 0(치환 검증)
- `fx_anu_key_sealed_only` → wake argv 에 executor key 부재 + ANU key 는 sealed loader 경유(literal 0)
- `fx_lock_contention` → 동시 2회 → wake 1회

## 4축 / active 경계 (★ 절대 섞지 말 것)
- 본 task 완료 = **implemented(코드) + verified(regression PASS) + wired 후보**(pickup runner 가 호출 가능 구조).
- **active 아님**. active 는 P0-b(OS-level event driver)가 result.json 감지→pickup→ANU wake→독립 세션 spawn→사람 개입 0 전이 5단계 증거 누적 시에만.
- 보고서/주석에 active/완료 주장 금지. "wired 후보 / active=P0-b 대기" 로만.

## 금지 (회장 verbatim)
PR #163 merge 금지 · PR #163 즉시 close 금지 · OS-level crontab/systemd/inotify 설치 금지 ·
push/PR 생성 금지 · merge/force/rebase/admin override 금지 · active/완료 주장 금지 ·
ANU key literal/argv/prompt 노출 금지 · expected_files 5개 밖 수정 금지 · 신규 watcher 활성화 금지.

## finalize (로컬 commit 까지만)
1. fresh main `5e714887` base worktree(task-2720). 2. `python3 -m pytest tests/regression/test_anu_result_pickup_runner_2720.py tests/regression/test_anu_owned_callback_enforcement_2717.py -q` 전 PASS.
3. `git diff --name-only origin/main` = expected_files 5개만. 4. **로컬 commit only. push/PR/merge 금지.**
5. `memory/events/task-2720.done` + `memory/reports/task-2720.md`(이식 sha / pickup runner entrypoint / lock·dedupe·quarantine·sealed-key 입증 / finish-task 치환 diff / regression 결과 / ★ active 미주장 명시).

## ANU 후속 (봇 아님)
ANU 로컬 commit 독립 재검증 → capability delta(callback_pickup: BLOCKED_BY_CAPABILITY → IMPLEMENTED_NOT_WIRED 또는 wired 후보) + PR 진입 가능 여부만 보고. push/PR 회장 승인 전 금지.

## allowed_resources

```yaml
allowed_resources:
  paths:
    - "dispatch/anu_owned_callback_enforcement.py"
    - "tests/regression/test_anu_owned_callback_enforcement_2717.py"
    - "dispatch/anu_result_pickup_runner.py"
    - "tests/regression/test_anu_result_pickup_runner_2720.py"
    - "scripts/finish-task.sh"
    - "memory/events/task-2720.done"
    - "memory/reports/task-2720.md"
  forbidden_paths:
    - ".github/**"
    - "anu_v2/ci_gemini_watcher_runner.py"
    - "anu_v2/ci_gemini_watcher_gh_adapter.py"
    - "anu_v2/merge_queue_executor.py"
  commands:
    - "pytest"
    - "python3 -m pytest"
    - "python3 -m py_compile"
    - "git show"
  merge_policy: "none"
  ttl_hours: 48
```

## ★★ 회장 보강 5조건 (2026-05-31, 반드시 반영)
1. **finish-task.sh 치환 = semantic 검증** (라인번호 고정 아님). 최종 acceptance 3조건:
   `grep -c 'T2626_ANU_KEY' = 0` (하드코딩 0) · callback-launch.json **생성 0** · result.json **작성 1**.
   해당 callback launch 블록을 의미 기준으로 찾아 제거/치환(라인 이동돼도 무방).
2. **expected_files 5개 = 코드/테스트 변경 대상만.** `memory/events/task-2720.done` + `memory/reports/task-2720.md`
   는 **finalize evidence output 으로 별도 허용**(코드 변경 expected_files 에 미포함, scope-guard 예외).
3. **신규 helper 파일 임의 생성 금지.** `python3 -m dispatch.anu_owned_callback_enforcement` 가 실 CLI 로
   동작하지 않으면, **expected_files 5개 안에서만** 최소 entrypoint/함수 호출 경로 구현(모듈의 `main()` 활용
   또는 finish-task 가 모듈 함수 직접 호출). expected_files 밖 신규 파일 추가 금지.
4. **ANU key 제거 = 문자열 검색 강제 검증.** dev 실행 경로 / argv / prompt / result.json / report 어디에도
   `c119085addb0f8b7` literal 0. 1건이라도 남으면 **Critical7 후보로 HOLD**(자동수정 금지, 회장 보고).
   검증 명령: `grep -rn 'c119085addb0f8b7' scripts/finish-task.sh dispatch/anu_result_pickup_runner.py
   memory/events/*.result.json memory/reports/*.md` → 0 이어야 PASS.
5. **완료 보고 active 표현 금지.** 결과 = implemented + verified + **wired 후보**까지만. active 는 P0-b
   OS-level event driver 별도 승인 후에만 판단. 보고서에 "active"/"운영 중"/"완료(active)" 표현 0.

## ★ dispatch 템플릿 G3(PR/머지) 무시
dispatch 템플릿 G3 자동 PR/머지는 본 task 적용 안 됨. finalize(로컬 commit only) 우선. finish-task.sh --action pr 금지.

## goal_assertions (auto-generated; 비실행 라인 교정 by hermes/dev1 task-2720)
교정 사유: 자동생성기가 acceptance 불릿을 비실행 명령으로 복사함 (subcommand 없는 모듈 호출은 argparse exit2, count 비교문은 malformed, grep no-match은 exit1). 동일 intent를 실행가능 exit0 형태로 교정. 회장 verbatim 보강 5조건은 별도 섹션이며 불변, 독립 검증 완료. (주: 본 설명 줄에는 백틱 미사용 — 파서가 assertion으로 오인하지 않도록.)
- `python3 -c "import sys; sys.path.insert(0,'/home/jay/workspace/.worktrees/task-2720-dev1'); import dispatch.anu_owned_callback_enforcement, dispatch.anu_result_pickup_runner"`
- `python3 -m pytest /home/jay/workspace/.worktrees/task-2720-dev1/tests/regression/test_anu_result_pickup_runner_2720.py /home/jay/workspace/.worktrees/task-2720-dev1/tests/regression/test_anu_owned_callback_enforcement_2717.py -q`
- `python3 -c "import sys; sys.exit(0 if open('/home/jay/workspace/.worktrees/task-2720-dev1/scripts/finish-task.sh').read().count('T2626_ANU_KEY')==0 else 1)"`
- `python3 -c "import sys; src=open('/home/jay/workspace/.worktrees/task-2720-dev1/dispatch/anu_result_pickup_runner.py').read(); sys.exit(0 if ('COKACDIR_KEY_'+'ANU') in src else 1)"`
