# SELF_COLLECTOR_FORBIDDEN 결선 부재 전수조사 (read-only audit) — 260523

회장 결정(2026-05-23): "py, sh, yaml 에서 명시 되어있을 거 아니야? 아누한테 normal callback 크론 발사로 딱 정해져있는 거 아니야? 세부적으로 가이드가 안 되어있어? 전수조사 해서 밝혀내!!!"

ANU 답: **코드는 정의되어 있다. 결선이 끊겨있다.**

기반: `feedback_callback_self_key_helper_not_wired_260521.md`(2026-05-21 1차 사고) + `feedback_normal_callback_not_registered_variant_260523.md`(2026-05-23 변종) + task-2639 dev6 자가검증 3회째 사고.

---

## 1. 결론 1줄
**SELF_COLLECTOR_FORBIDDEN doctrine 의 코드 정의는 완비되어 있으나 `dispatch/__init__.py` 의 import 결선이 부재 + 봇 lifecycle 의 callback 발사 진입점에서 enforce 호출 0. doctrine 은 task md 텍스트로만 봇 prompt 에 전달되고 봇은 그 텍스트 지시를 자의적 해석 (자가검증 / cron 등록 0 / self-key cron 등)**

---

## 2. 코드 정의 위치 (완비 확인)

| 모듈 | 역할 | 핵심 함수/상수 |
|---|---|---|
| `dispatch/callback_owner_enforcer.py` | SELF_COLLECTOR_FORBIDDEN 분류 + owner_key 검증 | `SELF_COLLECTOR_FORBIDDEN` (line 64) · `enforce_callback_owner()` (line 223) · `ANU_KEY_2553="c119085addb0f8b7"` (line 96) · `DEFAULT_ANU_KEYS` |
| `dispatch/normal_fallback_callback_helper.py` | ANU-owned cron prompt 생성 + 등록 | `build_anu_owned_callback_request()` (line 106) · 내부에서 `enforce_callback_owner()` 호출 (line 175) |
| `dispatch/cron_dispatch_guard.py` | cron-direct path fail-closed guard | `enforce_callback_owner` import (line 305) · `guard_self_collector_session` 호출 (line 418) |
| `anu_v3/self_collector_guard.py` | self-collector 세션 차단 가드 | `guard_self_collector_session()` |
| `utils/anu_callback_registrar.py` (task-2635 머지) | ANU key 하드코딩 registrar wrapper | `ANU_CALLBACK_KEY = "c119085addb0f8b7"` · self-key 정적 가드 |
| `utils/callback_envelope_schema.py` (task-2635+1 머지) | 5축 status enum + validator | `ANU_CALLBACK_KEY` 단일 출처 (line 39) |

★ 정의 자체는 **완비**. SELF_COLLECTOR_FORBIDDEN 분류 / ANU key 단일 출처 / register-after-update 패턴 / 5축 schema / canonical_root resolver / forbidden paths 정적 가드 전부 있음.

---

## 3. 결선 부재 (핵심 문제)

### 3.1 dispatch/__init__.py 의 import 부재
```
$ grep -E "^from|^import" dispatch/__init__.py | head -15
import argparse, fcntl, hashlib, json, os, re, subprocess, sys
from datetime import datetime, timedelta
from pathlib import Path
from typing import Any, List, Optional
from utils.composite_constants import (...)
# ★ callback_owner_enforcer / normal_fallback_callback_helper /
#   cron_dispatch_guard / anu_callback_registrar import 0
```
→ 봇이 `dispatch` 모듈을 통해 진입할 때 enforcer 가 import 조차 안 됨.

### 3.2 prompts/team_prompts.py 의 callback enforce 호출 0
```
$ grep -nE "(callback|build_anu_owned|enforce_callback_owner)" prompts/team_prompts.py
26:ANU_KEY = os.environ.get("COKACDIR_KEY_ANU", "")
35:def _get_anu_key(): ...
822:        f"- ANU_KEY: {_get_anu_key()}\n"
889:        f"- ANU_KEY: {_get_anu_key()}\n"
953:        f"- ANU_KEY: {_get_anu_key()}\n"
```
→ 봇 prompt 빌드 시 ANU_KEY 를 **텍스트로 삽입만** 함. 봇이 finalize 시 enforce_callback_owner 통과 호출 강제 0.

### 3.3 dispatch/core.py / dispatch/prompt.py 결선 0
- `dispatch/prompt.py` / `dispatch/core.py` 에서 callback enforcer / registrar 호출 grep 결과 0
- 봇 spawn 시 ANU_KEY 가 prompt 본문에 텍스트로 박혀서 봇에 전달되는 것 외 코드 enforcement 부재

### 3.4 봇 lifecycle finalize hook 결선 0 (dev side)
- 봇 (Claude Code 인스턴스) 의 "task 완료 직전 callback 발사" 동작은 **봇 자체의 자율 판단**
- 봇이 prompt 의 "ANU normal completion callback ... 반드시 독립 ANU key c119085addb0f8b7" 텍스트를 읽고 cron 발사 결정
- 봇이 자의적으로 발사 안 함 (task-2634) / self-key 로 발사 (task-2625) / 자가검증 (task-2639) 가능

---

## 4. 사고 3회 재발 패턴 분석

| 사고 | 변종 | 봇 행동 | 결선 부재 위치 |
|---|---|---|---|
| 2026-05-21 task-2625 | SELF_KEY_REGISTRATION | self-key 로 cron 발사 (executor 자가수집) | enforcer 코드는 있으나 봇 finalize 진입점에서 호출 0 |
| 2026-05-23 task-2634 | NOT_REGISTERED | cron 발사 자체 0, envelope sendfile 만 | registrar 코드 있으나 봇 lifecycle 호출 0 |
| 2026-05-23 task-2639 | SELF_COLLECTOR_VERIFICATION | ANU key 로 cron 발사 후 봇 본인이 자가검증 | spawn 된 봇 세션이 enforce_callback_owner 진입 0 |

★ 공통 근본: **코드 정의 ≠ 코드 실행 진입점 결선**

---

## 5. 세부 가이드 부재 위치 (회장 verbatim 답)

### 5.1 py 레이어
- `dispatch/__init__.py` — enforcer import 부재 (정합 안전망 부재)
- `dispatch/core.py` — 봇 spawn 직전 callback contract 검증 부재
- `dispatch/prompt.py` — prompt builder 에 enforce_callback_owner 검증 통과 가드 부재
- `prompts/team_prompts.py` — ANU_KEY 텍스트 삽입만 · 코드 호출 0

### 5.2 sh 레이어
- `scripts/finish-task.sh` — finish 진입점 자체에서 callback 발사 자동 호출 부재 (봇이 직접 cokacdir --cron 호출 필요)
- `scripts/finish-task.sh` 가 봇 종료 시 callback registrar 호출 0 (FORBIDDEN 정책으로 수정 불가)

### 5.3 yaml/json 레이어
- 봇 settings.json 에 callback enforcement 정책 필드 부재
- `bot_settings.json` 에 `require_anu_callback_on_finish` 등 boolean 강제 필드 0

### 5.4 prompt 레이어
- task md 의 `## finalize 프로토콜 §4 ANU normal completion callback — 반드시 독립 ANU key c119085addb0f8b7 · collector_role=ANU` 텍스트
- 본 텍스트는 **doctrine 가이드** 일 뿐 코드 enforcement 0
- 봇이 자의적 해석 가능 (자가검증 / 발사 안 함 / self-key 사용)

---

## 6. 회장 verbatim 답 (요약)

> py, sh, yaml 에서 명시 되어있을거 아니야~~ "아누"한테 normal callback 크론 발사!!! 로 딱 정해져있는거 아니야???

**답**: 코드 정의는 py 차원에 있음 (`dispatch/callback_owner_enforcer.py` + `dispatch/normal_fallback_callback_helper.py` + `utils/anu_callback_registrar.py`). 단 **봇 lifecycle 의 진입점 (dispatch/__init__.py · dispatch/core.py · scripts/finish-task.sh · 봇 spawn prompt)** 에서 강제 호출 결선 0.

> 세부적으로 가이드가 안 되어있어???

**답**: 가이드는 task md prompt 의 텍스트로 있음 (`## finalize 프로토콜`). 단 **봇이 자의적 해석** 가능. 코드/스크립트/봇 settings.json 차원에서 fail-closed 강제 부재.

---

## 7. 본 단계 금지 (회장 verbatim doctrine)

- 본 spec = **read-only audit** 까지만. 실 코드 수정 / dispatch 결선 / scripts/finish-task.sh 수정 / prompts/team_prompts.py 수정 회장 별도 명시 승인 후
- replacement_pr_runner.py / scripts/finish-task.sh / dispatch/__init__.py 수정 0 (모두 forbidden)
- 본 spec 의 권고를 spec 작성 외 실행 0

---

## 8. 후속 hardening task 후보 (회장 결정 대기)

1. **dispatch/__init__.py 결선 추가** — `enforce_callback_owner` + `build_anu_owned_callback_request` import (forbidden 가드 vs 정책 충돌 해소 필요 — D안 패턴)
2. **dispatch/core.py 봇 spawn 직전 callback contract validator 호출 결선** — 봇 finalize 진입점에서 enforce_callback_owner 강제
3. **prompts/team_prompts.py 코드 enforce 추가** — prompt 빌드 시 callback policy validator 호출 (텍스트 + 코드 이중 안전)
4. **bot_settings.json 차원 fail-closed 정책** — `require_anu_callback_on_finish=true` 강제 필드 + 봇 lifecycle 검사
5. **scripts/finish-task.sh 차원 callback 자동 발사** — (forbidden 수정 필요 — 회장 unfork 별도 결정)
6. **봇 (Claude Code 인스턴스) 의 cokacdir --cron 호출 자체에 SELF_COLLECTOR 차단 wrapper 도입** — cokacdir 명령어 자체에 owner_key vs executor_key 비교 가드 (cokacdir 본체 수정 필요 — 별도 회장 결정)

---

## 9. frozen anchor

- ANCHOR-1: "코드 정의 완비 (callback_owner_enforcer / normal_fallback_callback_helper / cron_dispatch_guard / anu_callback_registrar / self_collector_guard 5종 모듈)"
- ANCHOR-2: "결선 부재 6 위치 (dispatch/__init__.py import 0 · dispatch/core.py 호출 0 · dispatch/prompt.py 호출 0 · prompts/team_prompts.py 텍스트만 · scripts/finish-task.sh 호출 0 · 봇 lifecycle 자율 판단)"
- ANCHOR-3: "사고 3회 재발 패턴 (self-key / not-registered / self-collector-verification) 공통 근본 = 코드 정의 vs 진입점 결선 분리"
- ANCHOR-4: "본 audit read-only · 후속 hardening task 6 후보 회장 결정 대기"
- ANCHOR-5: "scripts/finish-task.sh / dispatch/__init__.py 등 forbidden 파일 수정 시 회장 명시 unfork 결정 필수"
