# SELF_COLLECTOR enforcement wiring hardening batch spec — 260523

회장 결정(2026-05-23 PR #142 ACCEPT 직후): `audit_self_collector_enforcement_gap_260523.md` 전수조사 결과 수락 후 즉시 wiring batch. **task md 텍스트 doctrine 만으로는 봇 자의적 해석으로 SELF_COLLECTOR 변종 3회 재발 (task-2625/2634/2639). callback owner enforcement 를 runtime path 에 강제 결선.**

기반: `audit_self_collector_enforcement_gap_260523.md` + `feedback_callback_self_key_helper_not_wired_260521.md` + `feedback_normal_callback_not_registered_variant_260523.md` + `system_normal_callback_registration_implementation_spec_260523.md` + PR #142 (`93598660`) snapshot crossref 정책.

---

## 1. 본 batch 범위 (Track A/B/C/D 병렬)

### Track A — dispatch entrypoint hardening
- `dispatch/__init__.py` — `enforce_callback_owner` + `build_anu_owned_callback_request` + `validate_callback_envelope_owner` import 결선
- `dispatch/core.py` — 봇 spawn 직전 callback contract validator 호출 (prompt 빌드 전 통과 검증)
- `dispatch/prompt.py` — prompt 생성 전에 enforce guard 통과 확인 (ANU_KEY 단일 출처 정합 + collector_role=ANU 강제)

### Track B — prompt/lifecycle hardening
- `prompts/team_prompts.py` — ANU_KEY 텍스트 삽입 + 코드 contract 참조 연결
  - prompt 빌드 시 `enforce_callback_owner` 통과 단언 (텍스트 + 코드 이중 안전)
  - task md prompt 본문에 "SELF_COLLECTOR 차단 / SENDFILE_ONLY 차단 / NOT_REGISTERED 차단" 명시 트리거 텍스트 자동 주입

### Track C — bot settings policy
- `bot_settings.json` (또는 동등) — `require_anu_callback_on_finish=true` 정책 필드 도입
- 봇 lifecycle 의 finalize 진입점에서 정책 필드 검사 → 미등록 종료 fail-closed
- Track A/B 와 expected_files overlap 0 (별도 settings 파일 영역)

### Track D — finish-task.sh / cokacdir wrapper (read-only spec 만)
- `scripts/finish-task.sh` 수정 **금지** (forbidden)
- `cokacdir` CLI 본체 수정 **금지**
- 단, 후속 unfork 필요성 / 정책 / 위험도 분석 spec 만 작성
- 별도 회장 결정 시 unfork task 발행 가능

---

## 2. Track A — dispatch entrypoint 결선 (정본)

### 2.1 신규 helper / 검증자
- `dispatch/spawn_callback_contract_validator.py` (신규) — 봇 spawn 직전 호출
  - `validate_spawn_callback_contract(task_id, executor_key, anu_key, prompt_text) -> ContractValidationResult`
  - 검증: executor_key ≠ anu_key (self-key 차단) · prompt 에 ANU_KEY 텍스트 포함 · collector_role=ANU 명시
  - 실패 시 dispatch 진입 차단 (NO_OP_SPAWN_CONTRACT_FAILED)

### 2.2 dispatch/__init__.py 결선
신규 import:
```python
from dispatch.callback_owner_enforcer import (
    enforce_callback_owner,
    SELF_COLLECTOR_FORBIDDEN,
    COLLECTOR_ROLE_ANU,
    DEFAULT_ANU_KEYS,
)
from dispatch.normal_fallback_callback_helper import build_anu_owned_callback_request  # noqa: F401
from dispatch.spawn_callback_contract_validator import validate_spawn_callback_contract  # noqa: F401
```
**dispatch/__init__.py 자체는 forbidden** (PR #140 round-1 추가). 본 변경은 회장 명시 unfork 가 필요한 영역 → spec §6 회장 결정 대기.

### 2.3 dispatch/core.py — 봇 spawn 직전 호출 결선
- `dispatch_to_bot(task_id, bot_key, prompt) -> ...` 함수 진입 직후
- `validate_spawn_callback_contract` 호출 → 실패 시 fail-closed 반환
- subprocess `cokacdir --cron` 발사 0 (validator 통과 후에만)

### 2.4 dispatch/prompt.py — prompt 빌드 전 guard
- prompt body 빌드 함수 진입 시 `enforce_callback_owner` 통과 단언
- prompt 본문에 `## SELF_COLLECTOR 차단 doctrine` 섹션 자동 주입

---

## 3. Track B — prompt/lifecycle 결선

### 3.1 prompts/team_prompts.py 강화
- 기존 line 822/889/953 의 `f"- ANU_KEY: {_get_anu_key()}\n"` 텍스트 삽입을 코드 contract 단언 호출과 연결
- `enforce_callback_owner` 통과 → ANU_KEY 정합 → prompt 본문에 다음 명시 주입:
  ```
  ## SELF_COLLECTOR / SENDFILE_ONLY / NOT_REGISTERED 차단 (verbatim doctrine)
  - 본 task 종료 시 ANU key c119085addb0f8b7 로 normal callback cron 등록 **강제**
  - executor self-key 로 cron 등록 → SELF_COLLECTOR_FORBIDDEN
  - cron 등록 0 + sendfile 만 → NOT_REGISTERED (fail-closed)
  - 자가검증 (봇 본인이 callback 회수) → SELF_COLLECTOR_VERIFICATION_FORBIDDEN
  - 어떤 변종이라도 ANU normal collector 가 독립 spawn 되어야 함
  ```

### 3.2 prompt-level 명시 트리거
- task md prompt 본문 자동 주입 패턴 ("Verbatim doctrine block")
- 봇이 텍스트 미인식 시 dispatch 진입 자체 차단 (validate_spawn_callback_contract 가 prompt body 검사)

---

## 4. Track C — bot_settings.json 정책 (별도 settings 파일)

### 4.1 정책 필드 추가
```json
{
  "callback_policy": {
    "require_anu_callback_on_finish": true,
    "self_collector_forbidden": true,
    "sendfile_only_forbidden": true,
    "not_registered_forbidden": true,
    "anu_key_single_source": "c119085addb0f8b7",
    "fail_closed_on_violation": true
  }
}
```

### 4.2 lifecycle check
- `utils/bot_settings_policy_loader.py` (신규) — 설정 로드 + 정책 검사 helper
- 봇 lifecycle finalize 진입점에서 `require_anu_callback_on_finish=true` 검사
- 미등록 시 `BOT_FINALIZE_POLICY_VIOLATION` 분류 + executor exit !=0

### 4.3 Track A/B 와 overlap 분리
- bot_settings.json 자체는 별도 위치 (`/home/jay/.cokacdir/bot_settings.json` 또는 워크스페이스 settings) — Track A/B 와 file overlap 0
- 정책 로더 `utils/bot_settings_policy_loader.py` 신규 — utils 영역

---

## 5. Track D — finish-task.sh / cokacdir wrapper (read-only spec 만)

### 5.1 finish-task.sh forbidden 분석
- `scripts/finish-task.sh` 가 봇 종료 진입점이라 callback 자동 발사 가능
- 단 **회장 forbidden 명시** — 본 batch 수정 금지
- 후속 unfork 필요성:
  - finish-task.sh 가 환경변수 ANU_KEY 읽고 자동 cron 발사 추가 (옵션)
  - 봇이 finish-task.sh 호출 시점에 envelope 자동 빌드 + cokacdir --cron 호출 + schedule_id 회수
  - finish-task.sh 자체에 `require_anu_callback_on_finish=true` 정책 체크
- 본 batch = **분석만** · 코드 수정 0

### 5.2 cokacdir 본체 정책 분석
- `cokacdir --cron --key <KEY>` 호출 시 KEY ≠ DEFAULT_ANU_KEYS 면 SELF_COLLECTOR 경고
- 단 cokacdir 본체 = **별도 시스템 (회장 외부 서비스)** — 본 batch 수정 0
- 후속 unfork 필요성: cokacdir CLI wrapper 도입 (`utils/cokacdir_safe_wrapper.py`) 후보

### 5.3 본 Track 산출물
- `memory/specs/system_finish_task_cokacdir_unfork_analysis_260523.md` (read-only)
- finish-task.sh / cokacdir 수정 시 영향 / 위험 / 정책 정리만

---

## 6. 회장 결정 대기 항목 (forbidden unfork)

1. **dispatch/__init__.py 수정 시점** — forbidden 영역 unfork 필요 (PR #140 부터 forbidden 가드)
2. **dispatch/core.py 수정 권한** — 현재 봇 dispatch 본체 (회장 결정)
3. **dispatch/prompt.py 수정 권한** — prompt builder (회장 결정)
4. **prompts/team_prompts.py 수정 권한** — 봇 prompt 본문 (회장 결정)
5. **bot_settings.json 위치 및 수정 권한** — 본 stack 외 settings 파일 (회장 명시)
6. **finish-task.sh 수정 시점** — forbidden 영역 (회장 명시 unfork)

---

## 7. 안전 불변식 (회장 verbatim)

- replacement_pr_runner.py / finish-task.sh / merge_ready_classifier / merge_ready_dryrun_executor **무수정**
- cokacdir CLI 본체 **무수정**
- Track D 는 read-only spec 만
- ANU key `c119085addb0f8b7` 단일 출처 유지 (변경 0)
- envelope UTF-8 ≤3900 bytes 유지
- live cokacdir CLI 실호출 0 (regression mock)
- merge/push/PR/cron/admin override 호출 0
- real auto-merge activation 0
- PR #141 pilot 재시도와 섞기 0
- foreign dirty 정리 0
- ANU 직접 코드 구현 0 (dev bot 위임)
- expected_files 외부 수정 0
- BLOCKING_SECRET 0

---

## 8. 자동수렴
- Gemini medium/style/quality + expected_files 내부 → 자동수렴
- 동일 함수 HIGH 반복 시 LOOP_BOUNDARY → 회장 보고
- 회장 보고 트리거: Critical7 / credential expansion / expected_files 밖 / admin override / replacement_pr fail / post-merge smoke fail

---

## 9. 금지 (회장 verbatim)
- finish-task.sh 수정 금지
- cokacdir 본체 수정 금지
- replacement_pr_runner 수정 금지
- real auto-merge activation 금지
- PR #141 pilot 재시도와 섞기 금지
- foreign dirty 정리 금지
- ANU 직접 코드 구현 금지 (dev bot 위임)
- SELF_COLLECTOR hardening 외 task 와 혼합 금지

---

## 10. frozen anchor

- ANCHOR-1: "본 batch = SELF_COLLECTOR enforcement wiring 코드 결선 · Track A/B/C 구현 + Track D read-only spec"
- ANCHOR-2: "dispatch/__init__.py + core.py + prompt.py 결선 추가 (forbidden 영역 unfork 회장 결정 후)"
- ANCHOR-3: "prompts/team_prompts.py 텍스트+코드 이중 안전 (verbatim doctrine block 자동 주입)"
- ANCHOR-4: "bot_settings.json 의 require_anu_callback_on_finish=true 정책 도입 · 별도 settings 파일 영역"
- ANCHOR-5: "finish-task.sh / cokacdir 본체 무수정 · Track D 는 read-only 분석 spec 만"
- ANCHOR-6: "ANU key c119085addb0f8b7 단일 출처 유지 · 5축 + canonical_root 직교 유지"
- ANCHOR-7: "회장 forbidden unfork 결정 대기 6 항목 (dispatch/__init__.py · core.py · prompt.py · team_prompts.py · bot_settings.json 위치 · finish-task.sh)"
