# task-2717 — ANU_OWNED_CALLBACK_ENFORCEMENT 완료 보고 (헤르메스, dev1팀장)

> 회장 승인 dispatch. SELF_COLLECTOR 재발을 **문서가 아니라 executable
> automation** 으로 차단. 완료 신호 = result JSON 작성(callback 자가발사 0).
> 단일소스: `memory/tasks/task-2717.md`
> (sha256=5e8f234ee43addb9ce3b37d6f3c037c174d36f07a689135185a3b05f93e7f856, 검증 일치).

## 1. SCQA 요약

- **Situation**: dispatch 의 결선 누락 = executor 가 ANU-owned callback 을 실제로
  호출하지 못하고 자기 self-key 로 collector 를 흉내. 봇은 shell + 자기 key 보유
  → 물리적 차단 신뢰 불가.
- **Complication (incident task-2716+2)**: dev4 가 'ANU collector' 를 self-key
  (7943afbe…, schedule A372DD6B)로 발사. envelope 텍스트는
  `self_key_used=false / collector_role=ANU / "ANU key c119085addb0f8b7 사용"`
  주장 → 거짓. 실 owner = self-key. 기존 검증기들은 envelope 의 owner_key 텍스트를
  신뢰하거나(`normal_callback_registration_validator` source3,
  `validate_spawn_callback_contract`), actual owner 를 **caller 입력으로** 받아
  (`verify_owner`, `cross_check_four_sources`) — **누구도 실제로
  `cokacdir --cron-history` 를 ANU key 로 조회하지 않았다**. 이것이 결선 0.
- **Question**: callback 생성 권한을 executor 에게 주지 않으면서, self-fire 해도
  격리되게 하려면?
- **Answer**: **NEED 제거 + TRUST 제거** 양면 실행 구조 (신규 모듈 1 + 테스트 1).

## 2. 구현 (runtime-code-only, 2 파일)

신규 모듈 `dispatch/anu_owned_callback_enforcement.py` (commit `7f644a1a`).

### NEED 제거 — 봇이 self-fire 할 이유 없앰
- `executor_write_result_json(...)`: executor 완료조건 = **result JSON 작성 only**.
  `schedule_created=false`, `callback_fired=false` 를 산출물에 자기-증명으로 기록.
  Layer A / NO-CRON (schedule register 0, subprocess 0, callback fire 0).
- `anu_runner_pickup_and_fire(...)`: 별도 **ANU-key runner** 가 result JSON 을
  pickup → ANU-owned callback **발사 request(데이터 argv)** 생성. owner_key 가
  독립 ANU key 가 아니거나 executor self key 와 같으면 **fail-closed REFUSE
  (argv=None)**. 실 cron 발사는 이 함수가 하지 않는다 — argv 만 surface, 권한 있는
  ANU 세션이 실행(**자가발사 금지** = 본 task 가 고치는 그 패턴).

### TRUST 제거 — self-fire 해봤자 격리
- `resolve_authoritative_owner(sid, probe)`: envelope 의
  `self_key_used`/`collector_role`/`owner_key` 텍스트를 **불신**. ANU key 로
  `cokacdir --cron-history <sid>` 실조회(ownership-checked API):
  `status=ok` ⇒ ANU 소유(authoritative), `status=error`("not found"/"access
  denied") ⇒ 비-ANU 소유. (실측 메시지 `{"status":"error","message":"schedule
  not found or access denied: ..."}` 확인 후 파서 정렬.)
- `verify_collector_authoritative(...)`: 실 owner≠ANU →
  **`SELF_COLLECTOR_QUARANTINE`** 자동 격리 + `NON_AUTHORITATIVE`.
  `usable_for_auto_merge=False` (자동 push/merge 판단 사용 0),
  `work_preserved=True` (작업물 비파기/비차단). stale envelope·task_id mismatch 는
  사전 reject.
- `write_quarantine_record(...)`: 격리 증거를 durable 기록(삭제 금지 doctrine,
  독립 ANU 재검증으로 대체).

### 정렬 (중복 신설 0)
- owner-key 권위 게이트 = `callback_owner_enforcer.enforce_callback_owner` /
  `is_anu_key` **재사용**.
- ANU-owned argv 생성 = `normal_fallback_callback_helper.
  build_anu_owned_callback_request` **재사용**(lazy import — 결선 견고).
- task-2713(CALLBACK_LOCATION_METADATA, worktree-mismatch envelope 필드) /
  task-2715(REMEDIATION_HEAD_ADVANCE) 와 기능 중복 0 — 본 모듈은 callback
  **ownership(소유권) authority** 축만 담당.

## 3. 필수 regression (6) — 전부 PASS

`tests/regression/test_anu_owned_callback_enforcement_2717.py` → **7 passed**
(6 필수 + runner-refuse 1). 실 cron-history 응답 형태를 fake probe 로 주입,
실 spoof envelope 입력 → 자동격리 출력을 실행으로 입증.

1. dev self-key spoof envelope(텍스트 ANU, 실제 self-key A372DD6B) →
   **QUARANTINE** + 격리 artifact 기록. ★실 입력→자동격리 출력 입증.
2. access denied schedule(ANU key 조회 거부) → **비-ANU 판정**(OWNER_NOT_ANU).
3. stale envelope 재사용 → **reject**(48h 경과=STALE_ENVELOPE_REJECTED,
   task_id mismatch=ENVELOPE_BINDING_MISMATCH).
4. ANU-owned 정상 callback → **PASS(authoritative)**, usable_for_auto_merge=True.
5. result JSON pickup 정상 → executor 작성(schedule/발사 0) → **ANU runner 가
   ANU key argv 생성**; self-key runner 는 REFUSE.
6. self-collector quarantine 경로 → **격리 + 비권위(자동 push/merge 0) + 비차단
   (작업물 보존)**.

CLI 라운드트립 smoke 도 확인: `executor-write-result` → `anu-run`(ANU key argv
생성) → `anu-run`(self-key REFUSE, exit 2).

## 4. 격리/완료 신호 (self-collector 재발 방지 적용)

- **push 안 함**: dev bot token 부재 → 로컬 commit(`7f644a1a`)까지만. push 는
  ANU 독립 재검증 후 ANU OWNER FF.
- **완료 신호 = result JSON 작성**: `memory/events/task-2717.result.json`
  (module_path/runner_path/collector_verify_path/quarantine_path/
  regression_6_result/new_commit_sha/expected_files/ci_workflow_untouched 기록).
  callback **자가발사 0** — 본 task 가 고치는 패턴을 스스로 재현하지 않음.

## 5. 금지사항 준수

- PR #162 브랜치(`task/task-2716-clean-dev4`) **미접촉**. 새 worktree
  (`.worktrees/task-2717-dev1`, base origin/main 2321dd8b)에서만 작업. 그 branch 의
  430c58df→f85e7a89 advance 는 PR #162 **자체 독립 flow**(reflog 상 `task-2716+3`
  same-PR fix) — 본 task 개입 0.
- CI workflow(.github/workflows) 수정 0 · closeout_marker_watcher 신규 0 ·
  task-2703 혼입 0 · force/rebase/replacement PR 0 · finish-task 반복 0 ·
  Gemini unresolved 상태 MERGE_READY 선언 0.
- diff = runtime-code-only(module + test 2파일). callback/evidence/memory
  artifact 는 gitignored(`memory/events/task-*`) — PR diff 미포함.

## 6. 검증 명령

```
cd /home/jay/workspace/.worktrees/task-2717-dev1
python3 -m pytest tests/regression/test_anu_owned_callback_enforcement_2717.py -q   # 7 passed
python3 -m py_compile dispatch/anu_owned_callback_enforcement.py
git show --stat HEAD    # 2 files, +1207
```

## 7. ANU 다음 단계

ANU 독립 재검증(pytest 재실행 + diff=expected_files only + CI workflow 미변경
확인) 통과 시 회장 gate 하 ANU OWNER FF push → fresh Gemini → evidence-based
resolve. self-collector 검증주장 채택 0 — ANU 독립 재실행으로 확정.
