# task-2635 — normal callback registration enforcement (Core hardening · 코드 결선 · merge 실행 0)

- Level: Lv.3 (거버넌스 자동화 · callback enforcement hardening)
- 담당: dev6 페룬
- base: origin/main **5ffa87ae** (PR #137 shadow validation 머지 반영분)
- 단일소스 spec: `memory/specs/system_normal_callback_registration_implementation_spec_260523.md`
  - sha256: `0fbd1dad1e110c49474dfbdf13a21fb3bdd9c7f094128004dba8472840bb832d`
  - 정독 전 sha256sum 검증 필수, 불일치 시 중단·ANU 보고
- 보조 spec(read-only triage): `memory/specs/system_normal_callback_registration_enforcement_spec_260523.md`
- 사고 배경: `feedback_normal_callback_not_registered_variant_260523.md` (task-2634 NORMAL_CALLBACK_NOT_REGISTERED)
- 승인 범위: **callback hardening 까지만**. real auto-merge executor 활성화 미승인(회장 명시).

## 목표
executor 완료 시 ANU key normal callback registrar 를 **실제 코드로 결선**. sendfile/report 전송 ≠ callback 분리. `normal_callback_registration_status` 필수화 + 5값 enum. `NOT_REGISTERED`/`SENDFILE_ONLY`/`REGISTER_FAILED` fail-closed. fallback cancel-on-success success-signal 명확화. ANU collector spawn 검증 dry-run fixture+regression.

## 필수 구현 (spec §3, §4, §5, §6 정본)

### 신규 helper 4
1. `utils/anu_callback_registrar.py` — registrar 순수함수 + cokacdir --cron wrapper · ANU key `c119085addb0f8b7` hardcoded · self-key 차단 정적 가드 · envelope UTF-8 ≤3900 bytes
2. `utils/callback_envelope_schema.py` — `NormalCallbackRegistrationStatus` enum (5값) + `validate_envelope` + `is_success_status`
3. `utils/anu_callback_fallback.py`(또는 `dispatch/fallback_cancel_logic.py`) — fallback success-signal 판정
4. `dispatch/finalize_hooks.py` — `finalize_with_callback_registration(task_id, result, anu_key) -> finalize_result` lifecycle 결선

### 최소 결선 수정
- `dispatch/__init__.py` — 1줄 import 추가 (`from dispatch.finalize_hooks import finalize_with_callback_registration`)

### fixture 신규 (5 시나리오)
`tests/fixtures/normal_callback_registration/<scenario>/{evidence.json,expected.json,PROVENANCE.md}` (5×3 = 15 files)
- `registered_normal` → REGISTERED · fail_closed=false · fallback_cancel=true
- `not_registered_envelope_only` → NOT_REGISTERED · fail_closed=true · fallback_cancel=false
- `sendfile_only_no_cron` → SENDFILE_ONLY · fail_closed=true · fallback_cancel=false
- `register_failed_cli_error` → REGISTER_FAILED · fail_closed=true · fallback_cancel=false
- `skipped_explicit_reason_dryrun` → SKIPPED_WITH_EXPLICIT_REASON + reason · fail_closed=false · fallback_cancel=true

### regression 신규 4
- `tests/regression/test_anu_callback_registrar.py` — registrar ANU key 단언 + envelope ≤3900 + cron 호출 시그니처
- `tests/regression/test_callback_registration_enforcement.py` — 5 fixture finalize_result 단언 + fail-closed 5값 enum 분기
- `tests/regression/test_callback_vs_sendfile_separation.py` — sendfile_only 단독 callback 미인정 + 함수 책임 분리
- `tests/regression/test_collector_spawn_dry_run.py` — REGISTERED→spawn expected · NOT_REGISTERED→no-spawn · subprocess 실호출 0

### (선택)
- `tests/fixtures/normal_callback_registration/INDEX.md`

**총 25~27 files. 프로덕션 영향**: `dispatch/__init__.py` 1줄 import + (있는 경우) 기존 finalize 진입점 1줄 hook 호출. 그 외 신규/test.

## 안전 불변식 (spec §7)
- ANU key `c119085addb0f8b7` 하드코딩 단일 출처 — self-key 차단
- envelope UTF-8 ≤3900 bytes hard limit · 위반 시 REGISTER_FAILED
- `NOT_REGISTERED`/`SENDFILE_ONLY`/`REGISTER_FAILED` fail-closed (executor exit !=0 + fallback no-cancel)
- live cokacdir CLI 실호출 0 (regression dry-run · subprocess mock 또는 frozen fixture)
- merge/push/PR/cron/admin override 호출 0
- replacement_pr_runner.py / finish-task.sh / merge_ready_classifier / merge_ready_dryrun_executor **무수정**
- expected_files 외부 수정 0

## 필수 regression
- 신규 4 regression 전부 PASS
- 기존 shadow 156/156 + dryrun 58/58 + classifier 50/50 유지 (회귀 0)
- full `tests/regression` new fail 0 (3 pre-existing test_stash_origin_audit_compat 무관)
- live workspace/GitHub/git 의존 0

## 자동수렴 (spec §8)
- Gemini medium/style/quality + expected_files 내부 + Critical7 0 + credential expansion 0 → 자동수렴
- expected_files 내부 non-critical HIGH 도 자동수렴, 동일 함수 HIGH 반복 시 LOOP_BOUNDARY → 회장 보고
- 회장 보고 트리거: Critical7 / credential expansion / expected_files 밖 수정 / admin override / replacement_pr fail / smoke fail

## 금지 (spec §9 · 회장 verbatim)
- real auto-merge executor 구현 금지
- auto-merge 활성화 금지
- NL intake 코드 구현 금지
- foreign dirty 정리 금지
- replacement_pr_runner.py 수정 금지
- PR #137 산출물과 섞기 금지
- production service task 와 혼합 금지
- callback 재발사 실험 남발 금지
- merge_ready_classifier · merge_ready_dryrun_executor 본체 수정 금지

## finalize 프로토콜 (★ BOT App token 부재 — 로컬 한정 · spec §10)
1. base = 최신 origin/main 5ffa87ae clean worktree
2. 신규 helper 4 + fixture 15 + regression 4 + dispatch/__init__.py 1줄 import 전부 PASS + full new-fail 0
3. **로컬 commit만** (push/PR/merge 금지). finish-task.sh project_path 없이 로컬 종결
4. ANU normal completion callback — **★ 본 task 자체로 신규 registrar 사용 검증 (자기 hardening 실증)**: 구현한 `register_normal_callback` helper 호출로 cron 등록 + `normal_callback_registration_status=REGISTERED` envelope 회수
5. registrar 미완성 fallback: 기존 패턴 (`cokacdir --cron --key c119085addb0f8b7`) 직접 호출 — collector_role=ANU
6. callback envelope UTF-8 ≤3900 bytes · envelope: task_id=task-2635 · 로컬 commit SHA · result_path · report_path · 25~27 file 검증 요약 · regression 요약 · sha256 · **`normal_callback_registration_status=REGISTERED` 필드 명시 (본 task 자기검증)** · `callback_cron_schedule_id` 회수값
7. executor 시작/종료 ts·로컬 commit SHA 명기

## frozen anchor (D-SPEC-EXACTNESS · spec §11)
- ANCHOR-1: "executor 완료 시 ANU key normal callback registrar 실제 코드 결선 강제"
- ANCHOR-2: "registration_status enum 5값 · NOT_REGISTERED/SENDFILE_ONLY/REGISTER_FAILED fail-closed"
- ANCHOR-3: "sendfile/report ≠ callback — 함수/스키마 분리"
- ANCHOR-4: "fallback cancel-on-success = REGISTERED status + collector durable-success 마커"
- ANCHOR-5: "real auto-merge executor 미승인 — 본 task = callback hardening 까지만"
- ANCHOR-6: "replacement_pr_runner/finish-task.sh/merge_ready_classifier/merge_ready_dryrun_executor 무수정 · dispatch/__init__.py import 1줄 추가만 허용"
