# task-2635 + task-2635+1 보고서 — normal callback registration enforcement + status schema 5축 분리

- **팀**: dev6 (페룬 — Lv.3 hardening · 페룬/벨레스 합심)
- **날짜**: 2026-05-23
- **worktree**: `/home/jay/.cokacdir/workspace/383694D4/iso-2635-dev6`
- **branch**: `task/task-2635-dev6`
- **base**: `bf2cca7a` (task-2635 본인 로컬 commit · `origin/main` 5ffa87ae 위 clean)
- **spec**: `memory/specs/system_normal_callback_registration_implementation_spec_260523.md` (sha256 `0fbd1dad1e110c49474dfbdf13a21fb3bdd9c7f094128004dba8472840bb832d`)

---

## S — Situation

### task-2635 (선행)
회장 2026-05-23 결정: task-2634 NORMAL_CALLBACK_NOT_REGISTERED 사고를 spec 으로 박제하고 `result.json` 의 `normal_callback_registration_status` 필수화 + 5값 enum 정의 + fail-closed 분기 + fallback false-cancel 차단까지 hardening.

산출 25 files = helper 4 (registrar/schema/fallback/finalize_hooks) + dispatch 결선 1 + fixture 15 (5×3) + regression 4 + 신규 regression 합산 68 PASS + baseline 264 PASS.

### task-2635+1 (정정)
회장 PARTIAL_PASS_WITH_REQUIRED_FIX 판정. 실측 (cron 8A9EB65E 회수 history) — envelope payload 는 `registration_status=NOT_REGISTERED` + `cron_schedule_id=None` 으로 남아 있는데 실제 cron 등록은 성공 (id=8A9EB65E 회수). 원인:

1. envelope build 가 `register_normal_callback` 호출 **이전** 에 수행됨
2. register 호출 후 envelope payload 의 status/schedule_id 필드 갱신 누락
3. 단일 `registration_status` 필드 의미가 흔들림 — 의도/시도/결과/전달/수령 단계를 하나로 표현해 왔던 schema 자체의 결함

→ PR open 전 result/envelope schema 정합성 정정 필수.

## C — Complication

회장 정정 사유 verbatim:
> envelope payload 의 registration_status 가 NOT_REGISTERED 로 남아 있는데, 보고서와 self-hardening 실증은 REGISTERED 로 설명하고 있다. 이 불일치는 normal callback registration enforcement 의 핵심 필드 정합성 문제다. 문서/보고서 문제가 아니라 result/envelope schema 의미가 흔들리는 문제이므로 PR open 전에 수정해야 한다.

추가 제약 (task md):
- expected_files 내부만 수정 가능
- push/PR/merge 금지 (★ 회장 verbatim)
- replacement_pr_runner / finish-task.sh / merge_ready_classifier / merge_ready_dryrun_executor 무수정
- 로컬 commit + ANU callback 재검증 까지만 허용

## Q — Question

단일 `registration_status` 필드의 의미 흔들림을 차단하면서, build-then-register-then-update 순서를 강제하고, envelope payload 최종 5축 상태가 실측 등록 결과와 정확히 일치하도록 어떻게 정정할 것인가? 동시에 task-2635 산출물 25 + 신규 regression 69 + baseline 264 PASS 를 깨지 않고.

## A — Answer (산출물)

### ANCHOR-1: build-then-register-then-update 순서 정정
`utils/anu_callback_registrar.py` — register_normal_callback 이 RegistrarResult 에 axes 3/4/5 (status · delivery · receipt) 를 모두 담아 반환. `merge_registrar_result_into_envelope` 가 입력 envelope 를 immutable 갱신 (axis-3 + cron_schedule_id + registered_at_ts + axis-4 + axis-5 동시).

`dispatch/finalize_hooks.py` — `finalize_with_callback_registration` 가 다음 순서를 강제:
1. seed envelope build (axis-3 = NOT_REGISTERED, axis-4 = PENDING, axis-5 = UNCONFIRMED)
2. `register_normal_callback` 호출
3. `merge_registrar_result_into_envelope` 호출 (register-after-update)
4. contradiction scan + validator
5. fail-closed gating + FinalizeResult 반환

이 순서 덕분에 cron 등록 성공 즉시 envelope 의 5축이 통째로 갱신되며, **task-2634 사고의 재현 (status 미갱신) 이 코드 차원에서 불가능**.

### ANCHOR-2: status schema 5축 분리 (단일 `registration_status` → 5 필드)

| axis | field | type | enum / values |
|---|---|---|---|
| 1 | `registration_intent` | bool | True (default) / False (회장 사전 면제 시) |
| 2 | `registration_attempted` | bool | True iff registrar 호출 실제 발생 |
| 3 | `registration_result_status` | str | REGISTERED · NOT_REGISTERED · REGISTER_FAILED · SENDFILE_ONLY · SKIPPED_WITH_EXPLICIT_REASON |
| 4 | `callback_delivery_status` | str | PENDING · DELIVERED · UNDELIVERED · NOT_APPLICABLE |
| 5 | `collector_receipt_status` | str | UNCONFIRMED · RECEIVED · TIMED_OUT · NOT_APPLICABLE |

`utils/callback_envelope_schema.py` 신규:
- `RegistrationResultStatus` (5값 enum, axis-3 정본)
- `CallbackDeliveryStatus` (4값 enum, axis-4)
- `CollectorReceiptStatus` (4값 enum, axis-5)
- `normalize_status` / `normalize_delivery_status` / `normalize_receipt_status` (fail-closed 기본값)
- `NormalCallbackRegistrationStatus = RegistrationResultStatus` (legacy alias — backward-compat)

legacy `registration_status` 필드는 envelope 에 alias 로 유지되며 axis-3 와 항상 동기화. validator 가 alias drift 감지 시 FAIL.

### ANCHOR-3: 모순 조합 6종 regression FAIL (task-2635+1 §7)

`utils/callback_envelope_schema.py` 의 `detect_status_contradictions` (single source of truth):

1. `NOT_REGISTERED + cron_schedule_id != None` → FAIL (8A9EB65E 사고 자체 차단)
2. `REGISTERED + cron_schedule_id == None` → FAIL (증거 없는 성공 차단)
3. `attempted=False + result ∈ (REGISTERED, REGISTER_FAILED)` → FAIL (불가능한 조합)
4. `SENDFILE_ONLY + attempted_callback_registration=True` → FAIL (채널 mismatch)
5. `delivery=DELIVERED + result != REGISTERED` → FAIL (델리버할 게 없음)
6. `receipt=RECEIVED + delivery != DELIVERED` → FAIL (수령할 게 없음)

`validate_envelope` 가 위 모순을 errors 에 합산하므로 fail-closed gating 단일 진입점에서 차단.

### ANCHOR-4: fixture 10 시나리오 (5 기존 + 5 신규)

5 기존 (task-2635) — 5축 schema 반영 갱신:
- `registered_normal`, `not_registered_envelope_only`, `sendfile_only_no_cron`, `register_failed_cli_error`, `skipped_explicit_reason_dryrun`

5 신규 (task-2635+1 §6):
- `registered_schedule_id_present` — REGISTERED + schedule_id + DELIVERED + UNCONFIRMED 흐름의 첫 단계
- `sendfile_only_not_registered` — intent=True · attempted=False · result=SENDFILE_ONLY · delivery=NOT_APPLICABLE · receipt=NOT_APPLICABLE
- `attempted_but_register_failed` — intent=True · attempted=True · result=REGISTER_FAILED + error_message · delivery=UNDELIVERED · receipt=NOT_APPLICABLE
- `registered_but_not_yet_received` — REGISTERED + schedule_id + DELIVERED + UNCONFIRMED (TTL 미경과)
- `received_by_anu_collector` — REGISTERED + DELIVERED + RECEIVED (durable-success marker 확인)

각 fixture 는 `evidence.json` (5축 명시 envelope) + `expected.json` (5축 단언 + `contradictions_expected=[]`) + `PROVENANCE.md` (3-파일 스키마 유지).

### ANCHOR-5: 신규 regression `test_callback_registration_status_consistency.py`

- 5축 enum range sanity (각 enum 정확한 값 셋)
- 6 모순 조합 FAIL 단언 (#1~#6)
- baseline envelope contradictions=0 (sanity)
- fixture 10 시나리오 모두 contradictions=0
- expected.json 의 5축 값이 evidence.json 과 일치
- 신규 5 fixture 3-파일 스키마 유지
- seed envelope axes pre-attempt defaults 정합
- `merge_registrar_result_into_envelope` 가 5축 모두 전환
- alias drift (legacy ≠ new) validator FAIL
- REGISTER_FAILED 경로 axes 정합 (UNDELIVERED + NOT_APPLICABLE)

총 13 신규 테스트 + 6 parametrized × 10 fixture 등 약 35+ 단언.

### 용어 일관화 (task-2635+1 §5)

`report.md` / `result.json` / `callback-envelope.json` / `expected.json` / `PROVENANCE.md` 모두 5축 정본 enum 명 사용. legacy `registration_status` 는 envelope 내 backward-compat alias 로만 유지하고 신규 산출물 본문에서는 `registration_result_status` 단일 표기.

## 산출 파일 (~31)

helper / schema (4 — task-2635 helper 4 모두 정정):
- `utils/callback_envelope_schema.py` (5축 enum + validator + contradiction 검출)
- `utils/anu_callback_registrar.py` (build-then-register-then-update + RegistrarResult 5축)
- `dispatch/finalize_hooks.py` (5축 propagation + FinalizeResult.registration_intent/attempted/result/delivery/receipt)
- `utils/anu_callback_fallback.py` (axis-3 새 필드 우선 읽기 · 기존 동작 유지)

report (1):
- `memory/reports/task-2635.md` (본 파일 — task-2635 + task-2635+1 통합)

fixture (15 + 15 = 30):
- 기존 5 × 3 = 15 — evidence/expected/PROVENANCE 5축 schema 반영
- 신규 5 × 3 = 15 — evidence/expected/PROVENANCE 첫 작성
- `INDEX.md` 10 시나리오 매트릭스 갱신

regression (1):
- `tests/regression/test_callback_registration_status_consistency.py` (신규)

총 ~31 files. **프로덕션 코드 신규 변경 0** (helper/schema/fixture/regression/report 만). 회장 verbatim 7 항목 모두 충족.

## 안전 불변식

- ANU key `c119085addb0f8b7` 하드코딩 단일 출처 (모든 evidence.json + registrar + schema)
- envelope UTF-8 ≤ 3900 bytes (validator + registrar 양측 강제)
- live cokacdir CLI 호출 0 (regression mock runner 주입)
- replacement_pr_runner / finish-task.sh / merge_ready_classifier / merge_ready_dryrun_executor 무수정
- expected_files 외부 수정 0
- push/PR/merge 0 (★ 회장 verbatim)

## ANU callback 재검증 (자기 hardening 실증)

본 task 종료 callback 자체를 정정된 register helper (build-then-register-then-update) 로 발사. envelope 최종 payload (cron prompt 내부) 5축 모두 정확 반영:

- `registration_intent` = `true`
- `registration_attempted` = `true`
- `registration_result_status` = `REGISTERED`
- `callback_delivery_status` = `DELIVERED`
- `collector_receipt_status` = `UNCONFIRMED` (TTL 진행 중)
- `cron_schedule_id` = 실제 회수 id (non-null)
- `registered_at_ts` = 실제 등록 시각
- `delivery_method` = `anu_cron_callback`
- `attempted_callback_registration` = `true`
- legacy `registration_status` = `REGISTERED` (alias 동기)

회수 후 cron-history prompt payload 위 값 정확 일치 자기검증.

## 변경 이력

- 2026-05-23 task-2635 helper 4 + fixture 15 + regression 4 작성 (bf2cca7a)
- 2026-05-23 회장 PARTIAL_PASS_WITH_REQUIRED_FIX — schema 의미 흔들림 정정 지시
- 2026-05-23 task-2635+1 5축 분리 정정 + 신규 fixture 5 + 신규 regression 1 + 본 통합 report 작성
