# task-2629 완료보고 — CALLBACK_LIFECYCLE_CLASSIFIER_CORE (evidence-only decoupled · Phase 2 core)

- **task_id**: task-2629
- **team**: dev6-team (페룬)
- **work_level**: Lv.3
- **base_branch**: origin/main 4187332c (clean · anu_v3 불필요 · decoupled)
- **work_branch**: task/task-2629-dev6 @ commit 03c151cd
- **worktree**: /home/jay/.cokacdir/workspace/iso-2629-dev6
- **single_source_spec**: memory/specs/system_callback_lifecycle_state_schema_260522.md (회장 승인 2축 schema)
- **작성일**: 2026-05-22

---

## SCQA 요약

- **Situation**: 회장 결정(2026-05-22)으로 callback lifecycle 미수신 사건을 단일로 뭉개지 않고 2축(+root_cause 태그)으로 분류하는 evidence-only decoupled classifier를 Phase 2 **core only**로 코드화한다. anu_v3 미머지 산과 무관하게 독립 머지 가능해야 한다.
- **Complication**: anu_v3/callback enforcement(task-2627)는 origin/main 미반영이고, 메인 workspace에는 본 task 무관 foreign dirty 5건+이 잔존한다. 런타임 의존을 끌어들이거나 foreign dirty를 건드리면 새로운 미머지·오염 산이 생긴다.
- **Question**: anu_v3 런타임을 import하지 않고, frozen evidence snapshot만으로 (delivery_outcome / normal_callback_miss_cause / root_cause_tags / evidence_completeness)를 결정적으로 판정하는 순수 함수 classifier를 어떻게 독립 머지 가능하게 만들 것인가?
- **Answer**: `utils/`에 순수 함수 classifier + enum 단일소스 모듈을 신설하고, task-2625/2628/2628+1의 live evidence를 read-only로 캡처한 frozen fixture 3건 + UNKNOWN 1건으로 regression을 구성. 회장 필수 매핑 3건을 단일값 assert로 정확 재현(8 passed). anu_v3 import 0 정적 검증 포함. wiring은 task-2627 integration bundle로 handoff. push/PR/merge는 회장 승인 대기(로컬 micro-commit + reflection-ready).

---

## 완료 보고 8항목 (회장 verbatim §11)

### 1. classifier core 위치
- `utils/callback_lifecycle_classifier.py` — evidence snapshot(dict) → 2축/태그 판정 **순수 함수** `classify_callback_lifecycle(evidence, anu_keys=DEFAULT_ANU_KEYS)`.
- `utils/callback_lifecycle_states.py` — 축A/축B/축C/evidence_completeness/classification enum **단일소스** 상수 + `is_anu_owner_key()` 헬퍼.
- `utils/` 선택 근거: origin/main 존재·저결합·anu_v3 무관 → decoupled 적합(회장 권고와 일치).

### 2. core/wiring 분리 구조
- **포함(core)**: evidence → (축A,축B,축C,evidence_completeness) 판정 순수 함수 + frozen fixture + regression. 파일/네트워크/subprocess I/O 0, no daemon, read-only.
- **제외(wiring)**: executor completion contract / result.json fields 10~14 emit 결선 → 본 task 아님. task-2627 callback enforcement integration bundle로 이관(아래 7항 handoff).
- core는 anu_v3/callback enforcement 미머지 산과 무관하게 **독립 머지 가능**.

### 3. UNKNOWN / evidence_completeness 반영 방식
- 축A·축B에 `UNKNOWN_INSUFFICIENT_EVIDENCE` 값 추가. 증거가 없으면 추정하지 않고 UNKNOWN으로 둔다(환각/추정 금지 doctrine).
- `evidence_completeness` ∈ {COMPLETE, PARTIAL, MISSING} + `missing_evidence_sources`(결핍 소스 목록) 반환.
- core 소스(result_json/schedule_history/collectors)가 모두 부재 → MISSING → 축A=축B=UNKNOWN.
- CALLBACK_DELIVERY_GAP은 **residual only**: ANU key 등록+발사 + collector 부재 + BEFORE_FIRE 원인(git-gate/contract/self-key/envelope-not-fired) 전무일 때만(상태기계 순서로 자연 도출).

### 4. frozen fixture 구성
- `tests/fixtures/callback_lifecycle/{task-2625,task-2628,task-2628_plus_1,unknown_insufficient}/` 각각 `evidence.json`(입력) + `expected.json`(골든) + `PROVENANCE.md`(캡처 출처).
- evidence.json 구조(스펙 §6 소스 정규화): result_json(callback_registration_status/cron_id/owner_key/envelope_utf8_bytes), schedule_history(normal_callback/fallback owner key·발사·argv), escalate.reason, collectors(normal/self/fallback/manual artifact 존재·authoritative), applied_count, git(foreign_dirty/git_gate_blocked/checked_scope/scope_guard), reflection_merged, bot_app_token_present, self_key_fail_closed.
- 캡처 방식: `memory/events/task-262*.{result.json, escalate, independent_*.result.json, done.escalated}`에서 **read-only** 캡처 → freeze. 원본 무변경. regression은 frozen snapshot만 입력 → live workspace 상태(foreign dirty 정리·reflection 머지 등)에 **비의존**(결정적).

### 5. 3 fixture regression 결과
- `tests/regression/test_callback_lifecycle_classifier.py` — pure function · 실 cron 0 · 실 발사 0 · subprocess 0 · live 의존 0.
- 결과: **8 passed in 0.10s** (WORKSPACE_ROOT=worktree 및 env 미설정 양방향 동일).
- 회장 필수 매핑 단일값 assert (독립 재현 ALL PASS):
  - **task-2625**: delivery_outcome=MANUAL_ANU_REVERIFY · normal_callback_miss_cause=SELF_KEY_FIRED_NON_AUTHORITATIVE · root_cause_tags=[REFLECTION_NOT_MERGED] · classification=incident
  - **task-2628**: delivery_outcome=MANUAL_ANU_REVERIFY · normal_callback_miss_cause=FINISH_TASK_GIT_GATE_BLOCKED_BEFORE_CALLBACK · root_cause_tags=[FOREIGN_DIRTY_BLOCKER, GIT_GATE_SHARED_WORKSPACE_MISFIRE]
  - **task-2628+1**: delivery_outcome=FALLBACK_COLLECTOR_APPLIED · normal_callback_miss_cause=ENVELOPE_PREPARED_NOT_FIRED · root_cause_tags=[SELF_KEY_FAIL_CLOSED_BEFORE_FIRE, BOT_APP_TOKEN_ABSENT, REFLECTION_NOT_MERGED]
- 보강 검증: UNKNOWN(증거 결핍 → UNKNOWN+MISSING) 1건, CALLBACK_DELIVERY_GAP residual(positive/negative) 2건, 결정성(2회 동일) 1건, anu_v3 import 0 정적검증 1건.

### 6. anu_v3 dependency 없음 (grep 0 증명)
- `import anu_v3` / `from anu_v3` 구문 = **0건** (정규식 정적 검증):
  - `grep -rnE "^\s*(import\s+anu_v3|from\s+anu_v3)" utils/callback_lifecycle_*.py tests/` → 0건.
- raw `anu_v3` 토큰 언급 2건은 전부 docstring의 negation 설명("anu_v3 런타임 import 0", "anu_v3 런타임을 import 하지 않는다") — 실제 의존 아님.
- regression test `test_no_anu_v3_import_in_classifier_sources`가 import 구문 패턴을 정적으로 차단(상시 enforce).

### 7. wiring → task-2627 integration bundle handoff 목록
- (H1) executor completion contract에 result.json fields 10~14 emit: `delivery_outcome`(축A) / `normal_callback_miss_cause`(축B) / `root_cause_tags`(축C) / `lifecycle_state_evidence`(판정 근거 dict) / `classified_by`+`applied_count`.
- (H2) collector 경로(`dispatch/normal_fallback_callback_helper` launch_callback / fallback collector)에서 `<task>.callback_lifecycle.json` idempotent append(artifact writer) — classifier 호출 결과를 1회 기록(daemon/dual-purpose 금지).
- (H3) enum 단일소스 import 결선: dispatch/anu_v3 양쪽이 `utils/callback_lifecycle_states`를 단일 import(중복 taxonomy 신설 금지). ★ 단, anu_v3가 본 모듈을 import하는 것은 허용(역방향)이나 본 core는 anu_v3를 import하지 않음(불변).
- (H4) task-2627 callback enforcement(self-key fail-closed 런타임) origin/main 반영과 **동반 머지** 순서 정렬 — core 자체는 독립 머지 가능하나 wiring은 enforcement layer와 함께 진행.
- (H5) CALLBACK_ENVELOPE_ONLY 관측 태그는 enum에 정의되어 있으나 기본 미방출(회장 exact 매핑 보존). wiring 단계에서 명시 신호로 emit 정책 결정.

### 8. push/PR/merge 필요 여부 + 회장 결정 항목
- 본 task: push 0 · PR 0 · merge 0 (회장 승인 대기 · §10 금지 준수). 로컬 micro-commit 1건(03c151cd)만 수행.
- **회장 결정 필요 항목**:
  - (D1) core(branch task/task-2629-dev6) 독립 머지 승인 여부 — decoupled라 origin/main 단독 머지 가능.
  - (D2) FOREIGN_DIRTY_BLOCKER 해소 경로: (a) foreign dirty 5건 소유 task가 정리 후 finish, 또는 (b) clean isolated worktree(본 branch)를 origin/main 위에 finish(머지). ★ 봇은 foreign dirty 정리/머지 불가(forbidden·승인 대기).
  - (D3) wiring(H1~H4)을 task-2627 integration bundle로 진행 승인.

---

## 수정 파일별 검증 상태 (planned/verified)

| 파일 | 변경 내용 | grep 검증 | 상태 |
|------|-----------|-----------|------|
| utils/callback_lifecycle_states.py | 2축(+태그) enum 단일소스 + is_anu_owner_key | grep "ROOT_CAUSE_TAG_ORDER" OK | verified |
| utils/callback_lifecycle_classifier.py | evidence-only 순수 함수 classifier | grep "classify_callback_lifecycle" OK | verified |
| tests/regression/test_callback_lifecycle_classifier.py | 회장 매핑 3건+UNKNOWN+DELIVERY_GAP+결정성+anu_v3 정적 | grep "test_chairman_mapping_exact" OK | verified |
| tests/regression/conftest.py | worktree sys.path 격리(live main 오염 방지) | grep "_WORKTREE_ROOT" OK | verified |
| tests/fixtures/callback_lifecycle/task-2625/evidence.json | self-key 발화 frozen evidence | grep "D4CE6C8D" OK | verified |
| tests/fixtures/callback_lifecycle/task-2628/evidence.json | git-gate 차단 frozen evidence | grep "finish_task_git_gate_blocked" OK | verified |
| tests/fixtures/callback_lifecycle/task-2628_plus_1/evidence.json | fallback collector frozen evidence | grep "CEFB90DE" OK | verified |
| tests/fixtures/callback_lifecycle/unknown_insufficient/evidence.json | 증거 결핍 frozen evidence | grep "UNKNOWN" OK | verified |

- planned 항목 0건 (전부 verified). Edit 직후 grep 재확인 완료.

---

## L1 스모크테스트 결과 (필수 기록)

본 task는 순수 함수 라이브러리 모듈(서버/HTTP API 아님). L1 = 실제 모듈 import + 함수 실행 + regression.

- **서버 재시작**: 해당없음 (CLI/라이브러리 모듈 · 서버 프로세스 없음)
- **API 응답 확인**: 해당없음 (HTTP API 아님)
- **스크린샷**: 해당없음 (프론트엔드/UI 아님)
- **subprocess/실행 검증 (PASS)**:
  - `WORKSPACE_ROOT=<worktree> python3 -m pytest tests/regression/test_callback_lifecycle_classifier.py -q` → **8 passed in 0.10s** (exit 0).
  - 독립 호출 검증: 4개 fixture evidence.json 로드 → `classify_callback_lifecycle()` 실행 → 회장 매핑 3건 정확 재현 + UNKNOWN/결정성 ALL PASS (classifier 모듈이 worktree에서 로드됨을 `__file__`로 확인).
  - `python3 -m py_compile` 4파일 OK.
- **판정**: L1 통과 (end-to-end: evidence.json → classifier 순수 함수 → 회장 매핑 정확 산출 · live workspace 미오염 · 결정적).

---

## 발견 이슈 및 해결

1. **(해결) WORKSPACE_ROOT 없이 pytest 실행 시 import 실패**: 기존 `tests/conftest.py`가 `WORKSPACE_ROOT` 기본값(`/home/jay/workspace` live main)을 sys.path에 주입 → worktree의 신규 `utils.callback_lifecycle_*`가 live main에 없어 ModuleNotFoundError. 해결: `tests/regression/conftest.py` 신설 — worktree root를 sys.path[0] 우선 삽입 + stale `utils` 캐시 정리. WORKSPACE_ROOT 설정/미설정 양방향 8 passed 확인. (정상 checkout/머지 후엔 parents[2]==repo root이므로 no-op·benign.)
2. **(해결) anu_v3 정적 검증 false positive**: classifier docstring의 "anu_v3 import 0" 설명 문구가 단순 문자열 검사에 걸림 → import 구문 정규식(`^\s*(import|from)\s+anu_v3`)으로 교정. import 0 정확 검증.
3. **(해결) pyright 미사용 import**: 신설 conftest의 `import types` 미사용 → 제거.
4. **(범위 외·미해결·차단) FOREIGN_DIRTY_BLOCKER + GIT_GATE_SHARED_WORKSPACE_MISFIRE + SCOPE_GUARD_MAIN_HEAD_DIVERGENCE**: `finish-task.sh`를 실행(project_path 미지정 → 머지 skip)하니 **SCOPE-GUARD 단계(step 1.6)**에서 차단 + `.escalate`(reason=scope_guard_violation) 생성. 원인: scope guard가 **공유 메인 workspace의 `main(5a29d3ee)..HEAD(d8ea371f)` 384개 파일**을 검사 — 본 isolated worktree의 per-task diff가 아님. 본 task와 무관한 foreign 커밋/dirty(memory/specs, utils/replacement_pr_runner.py, 기타 anu/dispatch 계열 등)가 전부 VIOLATION으로 오탐됨.
   - **misfire 입증**: 본 worktree per-task diff(`origin/main..HEAD`) = **정확히 16개 파일, 전부 allowed_resources in-scope**(callback_lifecycle 관련). scope-diff.txt 384건 중 callback_lifecycle 파일 = 0(전부 foreign). 즉 차단은 공유 workspace 거버넌스 artifact이며 **본 task 산출물 결함 아님**.
   - **대응(회장 §8)**: foreign dirty 정리 금지(forbidden)·머지 금지(승인 대기)·반복 루프 금지 → micro-commit(03c151cd) + reflection-ready 종료. 자동 생성된 `.escalate`에 더해 `task-2629.foreign_dirty_blocker.json` + `.followup.txt`로 ANU 컨텍스트 보강. timer는 ESCALATED로 종료. **수동 .done 미생성**(gate 정당 차단 → blocked 상태가 정직). ANU/회장 판단 필요(머지 판단 D1/D2).

---

## 머지 판단 (Worktree 사용)

- **머지 필요**: Yes (단, ★ 회장 승인 전 금지 — §10. 봇 자체 머지 0)
- **브랜치**: task/task-2629-dev6 (@ 03c151cd, base origin/main 4187332c)
- **워크트리 경로**: /home/jay/.cokacdir/workspace/iso-2629-dev6
- **머지 의견**: core는 decoupled(anu_v3 import 0)라 origin/main 단독 머지 가능. regression 8 passed·회장 매핑 3건 정확. 충돌 가능성 낮음(전부 신규 파일: utils/callback_lifecycle_*.py, tests/fixtures/callback_lifecycle/**, tests/regression/test_callback_lifecycle_classifier.py + conftest.py). **차단 사유 = FOREIGN_DIRTY_BLOCKER(공유 workspace 거버넌스 이슈, 본 산출물 무관)**. 해소: foreign dirty 소유 task 정리 OR clean isolated worktree finish(회장 승인 시). G3 머지 게이트(PR→Gemini 리뷰)는 회장 머지 금지 지시로 보류.

---

## 모델 사용 기록

- 팀장(페룬, Opus): 설계·분배·통합·검증·보고(직접 코딩 0 — 알고리즘 사양만 정밀 작성 후 위임).
- 스바로그(백엔드, **sonnet**): utils/callback_lifecycle_states.py + classifier.py 구현.
- 벨레스(테스터, **sonnet**): frozen fixture 4건 + regression test 작성·실행.
- 라다(프론트)/모코시(UX): **미소환** — 본 task는 순수 백엔드/테스트(프론트·디자인 산출물 없음).
- 횡단조직(로키/마아트/야누스): 미소환 — Lv.3 normal 레벨, 보안/디자인 작업 아님(마아트 독립검증은 critical 레벨에서 의무).
- haiku 사용: 없음.

---

## 산출물 목록

- /home/jay/.cokacdir/workspace/iso-2629-dev6/utils/callback_lifecycle_states.py
- /home/jay/.cokacdir/workspace/iso-2629-dev6/utils/callback_lifecycle_classifier.py
- /home/jay/.cokacdir/workspace/iso-2629-dev6/tests/fixtures/callback_lifecycle/task-2625/evidence.json
- /home/jay/.cokacdir/workspace/iso-2629-dev6/tests/fixtures/callback_lifecycle/task-2625/expected.json
- /home/jay/.cokacdir/workspace/iso-2629-dev6/tests/fixtures/callback_lifecycle/task-2625/PROVENANCE.md
- /home/jay/.cokacdir/workspace/iso-2629-dev6/tests/fixtures/callback_lifecycle/task-2628/evidence.json
- /home/jay/.cokacdir/workspace/iso-2629-dev6/tests/fixtures/callback_lifecycle/task-2628/expected.json
- /home/jay/.cokacdir/workspace/iso-2629-dev6/tests/fixtures/callback_lifecycle/task-2628/PROVENANCE.md
- /home/jay/.cokacdir/workspace/iso-2629-dev6/tests/fixtures/callback_lifecycle/task-2628_plus_1/evidence.json
- /home/jay/.cokacdir/workspace/iso-2629-dev6/tests/fixtures/callback_lifecycle/task-2628_plus_1/expected.json
- /home/jay/.cokacdir/workspace/iso-2629-dev6/tests/fixtures/callback_lifecycle/task-2628_plus_1/PROVENANCE.md
- /home/jay/.cokacdir/workspace/iso-2629-dev6/tests/fixtures/callback_lifecycle/unknown_insufficient/evidence.json
- /home/jay/.cokacdir/workspace/iso-2629-dev6/tests/fixtures/callback_lifecycle/unknown_insufficient/expected.json
- /home/jay/.cokacdir/workspace/iso-2629-dev6/tests/fixtures/callback_lifecycle/unknown_insufficient/PROVENANCE.md
- /home/jay/.cokacdir/workspace/iso-2629-dev6/tests/regression/test_callback_lifecycle_classifier.py
- /home/jay/.cokacdir/workspace/iso-2629-dev6/tests/regression/conftest.py
- memory/events/task-2629.result.json (8항목 + callback 9 fields)
- memory/reports/task-2629.md (본 보고서)

---

## doctrines 준수

- D-SPEC-EXACTNESS · 단일소스 = system_callback_lifecycle_state_schema_260522.md
- evidence-only decoupled (anu_v3 import 0)
- frozen fixture (live 비의존 결정적 regression)
- 추정 금지 → UNKNOWN/INSUFFICIENT_EVIDENCE (환각 금지)
- CALLBACK_DELIVERY_GAP residual only
- no-dual-purpose / no-daemon (read-only 1회 판정 classifier)
- fresh worktree clean base (foreign dirty 회피)
- BOT App token 부재 → 로컬 micro-commit + reflection-ready
- attempt-1 only · spec drift 0
- ★ 본 산출물(classifier)을 본 task 자체 callback 미수신 분류에 dogfooding 하지 않음(§13 분류 대상↔도구 혼용 금지)

## 세션 통계
- 총 도구 호출: 0회

