# task-2720 — P0-a: callback argv-only 제거 + result.json pickup runner 결선 (보강 검증)

상태: **implemented + verified + wired 후보** · **active 아님(active 는 P0-b 별도 승인 후 판단)**
base: origin/main `5e714887` (fresh worktree, PR #163/#164/#165 worktree 와 격리)
finalize: **로컬 commit only** — push/PR/merge 0, PR #163 보존(머지/close 0).

## 1. expected_files (정확히 5개 — scope guard PASS)
`git diff --name-only origin/main` = 5 라인:
- `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` (surgical 치환)

evidence output(별도 허용, 코드 scope 외): `memory/events/task-2720.done` · `memory/reports/task-2720.md`.
신규 helper 파일 임의생성 0 — executor entrypoint 은 기존 모듈 `main()` 의 `executor-write-result` 서브커맨드 사용.

## 2. 빌딩블록 이식 (파일 1·2) — blob identity 입증
PR #163 head `b257f2aa` 에서 추출, **내용 수정 0** (blob SHA 일치):
- `dispatch/anu_owned_callback_enforcement.py` → blob `0f62028f2c482d480ffafac740e15b15cba2490a` (b257f2aa == HEAD MATCH)
- `tests/regression/test_anu_owned_callback_enforcement_2717.py` → blob `72cc7e3f30ade1cb8a7c168843e4b4e84762dc09` (b257f2aa == HEAD MATCH)

핵심 함수 보존 확인: `executor_write_result_json`(schedule_created=False, signal=RESULT_JSON_WRITTEN) ·
`anu_runner_pickup_and_fire`(executor self-key REFUSE) · `verify_collector_authoritative`(SELF_COLLECTOR_QUARANTINE / PENDING_OWNER_PROOF) ·
`FIRE_NOT_ACTIVATED` 기본 · `fire_callback_request(activate=True)→PHASE2_REQUIRED`(실발사 0).

## 3. pickup runner entrypoint (파일 3 신규)
`dispatch/anu_result_pickup_runner.py :129` —
`pickup_once(result_json_path, *, gh_probe=None, clock=None, sealed_key_loader=None) -> PickupResult`
fail-closed 처리순서 입증:
- result.json 파싱(없으면 `NO_RESULT_JSON`, 손상 FAIL) — L43/L159
- **lock**: `<task>.pickup.lock` `O_CREAT|O_EXCL|O_WRONLY` atomic create, 경합 시 no-op — L195/L199
- **terminal-marker no-op**: `<task>.pickup.done` / `<task>.pickup.acked` 존재 시 SKIP — L186/L187
- **dedupe**: 기존 `callback_4tuple_index.jsonl` 재사용 (task_id+sha256) — L55
- `verify_collector_authoritative` → PASS(ANU 소유)만 진행. `PENDING_OWNER_PROOF` 재시도(wake 0) — L259/L266
- **sealed-key proof**: ANU key 는 `utils.env_loader.load_env_keys()` → `os.environ[COKACDIR_KEY_ANU]` 로만 로드,
  미로드 시 fail-closed(wake 0) — `_default_sealed_key_loader` L63~71, `ENV_ANU_KEY="COKACDIR_KEY_ANU"` L59. argv/prompt/literal 노출 0.
- OS-level 설치/감시 driver 0 — `pickup_once` 는 함수(P0-b driver 가 호출). 부수효과(gh/clock/key/fs) 전부 주입형.

## 4. finish-task.sh 치환 (파일 5) — semantic acceptance 3조건 PASS
구 블록(`T2626_ANU_KEY="…"` 하드코딩 + `callback_kind=normal` envelope + `… > …callback-launch.json`)을
executor result JSON 작성 호출로 치환:
```
( cd "$WORKSPACE" && PYTHONPATH="$WORKSPACE" python3 -m dispatch.anu_owned_callback_enforcement \
    executor-write-result --task-id "$TASK_ID" \
    --result-dir "$WORKSPACE/memory/events" --report-path "memory/reports/${TASK_ID}.md" )
```
회장 보강 5조건 #1 — 의미 기준 검증 결과(라인이동 무방):
- `grep -c 'T2626_ANU_KEY' scripts/finish-task.sh` = **0** (하드코딩 제거)
- `callback-launch.json` **생성 = 0** (구 redirect 제거)
- `result.json` **작성 = 1** (executor result-dir 경로)
fail-closed 의미이전: result.json 미작성 시 `|| echo … fail-closed/non-blocking` 분기 유지.
cron 타이밍(`T2661_NORMAL_ABSOLUTE_AT`)은 normal 전용 라인이라 제거 — fallback dead-man 영향 0.

end-to-end smoke (격리 temp dir, 회장 보강 #1·#4 재검증):
result.json 1건 작성 · callback-launch.json 0 · result.json 내 ANU key literal 0 ·
`schedule_created_by_executor=false` · `callback_fired_by_executor=false` · `completion_signal=RESULT_JSON_WRITTEN`.

## 5. ANU key 제거 — 강제 문자열 검증 (회장 보강 #4, Critical7 gate)
task-2720 의 dev 실행 경로 / argv / prompt / result.json / report 에서 ANU 독립키 literal **0**:
- `scripts/finish-task.sh` = 0
- `dispatch/anu_result_pickup_runner.py` = 0
- `dispatch/anu_owned_callback_enforcement.py` = 0 (키는 `dispatch.callback_owner_enforcer` 의 import 상수만)
- executor 가 쓰는 `*.result.json` = 0 (smoke 검증)
- 본 report / `task-2720.done` = 0
→ **Critical7 gate: PASS (task-2720 도입분 0). 자동수정 없음.**

회장 가시성용 사전존재 관찰(본 task 도입분 아님, scope 외 · 무수정):
verbatim glob `memory/reports/*.md` 는 과거 다른 task(task-26.1 / task-262.1 / task-2635 / task-2673 /
task-2712 / task-388.1 외)의 **기존** report 에서 동 literal 을 매치함. 이는 수개월 전 정상 인가된
cron 등록 기록으로, task-2720 expected_files 밖이며 no-auto-fix 원칙 + scope guard 상 본 task 가 손대지
않음. 정리 필요 여부는 회장 판단 대상으로 보고만 함.

## 6. regression (전부 mock/fixture, 네트워크 0) — 28 passed
`python3 -m pytest tests/regression/test_anu_result_pickup_runner_2720.py tests/regression/test_anu_owned_callback_enforcement_2717.py -q`
→ **28 passed in 0.17s** (pickup 9 + enforcement 19).
pickup 9 fixture 매핑: fx_result_json_valid / fx_self_collector_key / fx_owner_proof_pending /
fx_duplicate_result_json / fx_terminal_marker_present / fx_invalid_task_id /
fx_finish_task_emits_result_json_only / fx_anu_key_sealed_only / fx_lock_contention.

## 7. 4축 / active 경계 (★ 미주장 명시)
본 task 완료 = **implemented(코드) + verified(regression 28 PASS) + wired 후보**(pickup runner 가 호출 가능 구조).
**active 아님.** active 는 P0-b(OS-level event driver: result.json 감지→pickup→ANU wake→독립세션 spawn→
사람개입 0) 5단계 증거 누적 + 별도 승인 후에만 판단. 본 보고서에 active/운영중/완료(active) 표현 0.
OS-level crontab/systemd/inotify 설치 0 · 신규 watcher 활성화 0.

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

---
finalize commits (로컬 only):
- `ab91983e` PR#163 head b257f2aa 모듈+test 이식 (blob SHA 일치)
- `51df16dd` pickup runner(신규)+regression(9 fixture)+finish-task.sh result.json-only 치환
- (본 커밋) evidence output: task-2720.done + task-2720.md

---

## 9. task-2720+1 — Gemini MEDIUM bounded auto-remediation (PR #166)

PR #166 Gemini unresolved finding을 **severity 아닌 validity/scope/repetition** 기준으로 bounded 1회 처리.
회장 인가(2026-05-31): Critical7 아님·핵심 안전게이트 통과. FIX 3건 / DISMISS 3건.

### FIXED (3건 — expected_files 내 bounded fix)
- **pickup_runner.py:206 (Gemini HIGH, stale lock)** — `O_CREAT|O_EXCL` lock이 직전 pickup 프로세스의 비정상 종료(SIGKILL/시스템 다운) 시 영구 잔존 → 해당 task_id의 모든 pickup이 영구 `SKIP_LOCK`으로 차단되는 **liveness 결함**. `FileExistsError` 경로에 `_STALE_LOCK_SECONDS=600` mtime 임계 기반 회수 추가(임계 초과 시 unlink+재획득, 회수 경쟁은 정상 경합 처리). mtime은 실 wall-clock이므로 주입 clock이 아닌 `time.time()` 비교. 신규 regression `test_fx_stale_lock_reclaimed` 추가(28→29).
- **pickup_runner.py:346 (Gemini MEDIUM, done_path_out)** — `marker_path=done_path_out`(line 362)에서 실제 소비되는 변수의 조건부 annotation(except 블록 내부)을 try 전 `done_path_out: Optional[str] = None` 초기화로 정리(possibly-unbound 정적분석 가드, 동작 동일).
- **finish-task.sh:1561 (Gemini MEDIUM, 2>/dev/null)** — executor-write-result(P0-a 핵심 산출물 result.json 작성) subprocess의 `2>/dev/null` 제거. write 실패 시 traceback이 silent 차단되던 리스크를 표면화(`||` 비차단 유지).

### DISMISSED (3건 — validity/scope 미달)
- **이식모듈:466 (Gemini HIGH, _parse_timestamp tz-offset)** — PR#163 blob 보존 대상. 실제 envelope writer는 `_iso()`(line 145/689)가 `%Y-%m-%dT%H:%M:%SZ` **Z-suffix**로 작성 → `_parse_timestamp_value` format(1)이 정확히 파싱. tz-offset(+09:00)은 P0-a 실동작에서 생성되지 않음 → **P0-a를 명백히 깨지 않음** → blob 변경 금지 원칙 유지. blob SHA 일치 보존.
- **pickup_runner.py:219 (Gemini MEDIUM, dedup 문자열 prefilter)** — dedup 정확성에 결함 없음. json.loads 전 `in` 필터는 순수 성능 최적화(현 ledger 규모에서 불필요) → dismiss.
- **pickup_runner.py:232 (Gemini MEDIUM, ledger O(N)→SQLite)** — SQLite/KV store 도입 제안 = over-engineering(premature optimization) → dismiss.

### bounded 경계
동일 semantic 재발 시 LOOP_BOUNDARY로 보고하고 추가 fix 금지(회장 verbatim). 본 +1은 1회 수렴.

### 검증
- regression `test_anu_result_pickup_runner_2720.py` + `test_anu_owned_callback_enforcement_2717.py` = **29 passed**(28→29).
- 우리 fix diff = 3파일(pickup_runner.py, finish-task.sh, test_*_2720.py). ANU key literal 0. _parse_timestamp 미변경(blob 보존).
- L1 실 subprocess: stale lock 회수 후 진행✓ / fresh lock SKIP_LOCK 유지✓ / result.json 작성(RESULT_JSON_WRITTEN)✓ / 에러 표면화✓.
---

## task-2720+4 (4차) 처리내역

**일자**: 2026-06-01  **담당**: 불칸(백엔드, sonnet)

**내용**: done_path atomic write micro-fix (4차 예외 승인).  
`open(done_path,"w")` 직접 기록 → `tmp_path=f"{done_path}.tmp-{os.getpid()}"` 임시 기록 + `os.fsync` + `os.replace` 원자 교체로 변경. OSError 시 tmp best-effort unlink. done_content/스키마/반환값 의미 변경 0. commit `85329563` (1 file changed, +9 -2).

**검증**: regression 32 PASS. L1 스모크테스트 PASS (정상 시 done_path 생성·tmp 잔존 0, 실패 모의 시 done_path 미생성·tmp 정리). finish-task.sh 완료.

**상세**: `/home/jay/workspace/memory/reports/task-2720+4.md` 참조.
