# P2 Exit Path Coverage Audit — finish-task.sh × R1~R5 (260529)

★ **READ-ONLY EVIDENCE AUDIT** — 회장 verbatim "P2 설계 검증 수행 · read-only 만 · 코드 수정 금지 · dispatch 금지 · PR 금지 · branch push 금지 · merge 금지" 정합. ANU 자체 분류 결정 0 · 회장 verbatim 매핑만.

---

## 0. Scope Anchor

- **target_file**: `scripts/finish-task.sh` (★ 1569 라인 · sha256 read-only audit)
- **target_contract_rules**: R1~R5 (★ memory/specs/p2_finish_task_callback_before_failfast_contract_draft_260529.md verbatim)
- **target_exit_points**: 41 (★ grep evidence)
- **doctrine_anchor**: "evidence 만 따른다" · "추정은 hypothesis 로 분리"

---

## 1. STEP 1 — 모든 exit path 수집 + 9 분류

### 1.1 전체 41 exit point 박제 (★ verbatim grep)

41 exit points across 1569 lines. Two callback layers exist in finish-task.sh:
- **Layer 1 (L1106-1160)**: NORMAL-CALLBACK-ENFORCE validator (★ bot self-registration check + 4-source validator)
- **Layer 2 (L1532-1558)**: callback runtime enforcement gate (★ helper launch 실제 registration)

### 1.2 9 분류 박제 (★ 회장 verbatim 정합)

| classification | 라인 | count |
|---|---|---|
| **success_exit (main)** | L67, L80, L510, L526 | 4 |
| **success_exit (python helper)** | L117, L132, L161, L192, L203, L208, L211, L222 | 8 |
| **qc_fail** | L359, L377 | 2 |
| **scope_guard_fail** | L451, L707 | 2 |
| **taskctl_fail** | L483, L492 | 2 |
| **dirty_workspace_fail** | L1373, L1512 | 2 |
| **escalation_path** | L470, L505, L521, L1012 | 4 |
| **callback_path** | L1149 (NORMAL-CALLBACK-ENFORCE 자체 FAIL) | 1 |
| **fail_fast_exit (generic)** | L282, L321, L531, L535, L571, L616, L717, L779, L804, L976, L1015, L1052, L1088, L1197, L1255, L1290 | 16 |

**총 41 = 12 success + 29 fail_fast (★ qc_fail/scope_guard/taskctl/dirty_workspace/escalation/callback/generic 합산)**

### 1.3 Layer 1 vs Layer 2 callback gate position

- Layer 1 NORMAL-CALLBACK-ENFORCE validator: **L1106-1160** (★ envelope 존재 시 validator 실행 · validator FAIL 시 L1149 exit 1)
- Layer 2 callback runtime enforcement gate: **L1532-1558** (★ helper launch 실제 cron registration)
- 모든 fail-fast exit 1 path: **L1106 이전** (★ Layer 1 미진입)
- success exit 0 cancellation (L67/L80): **L1106 이전** (★ Layer 1 미진입)
- BLOCKED/ESCALATED exit 0 (L510/L526): **L1106 이전** (★ Layer 1 미진입)

---

## 2. STEP 2 — 41 × 6 컬럼 매트릭스

| exit_point | classification | current_callback_reachable | callback_kind | current_result | P2_rule_coverage | coverage_gap |
|---|---|---|---|---|---|---|
| L67 | success_exit (cancellation) | NO | normal? | callback 미등록 (intentional?) | R1/R3 미적용 (★ cancellation 명시 0) | **GAP-1** |
| L80 | success_exit (cancellation) | NO | normal? | callback 미등록 | R1/R3 미적용 | GAP-1 |
| L117 | python helper success | N/A | — | helper 내부 종료 | spec 적용 외 (★ nested helper) | **GAP-2** |
| L132 | python helper success | N/A | — | helper 내부 종료 | spec 적용 외 | GAP-2 |
| L161 | python helper success | N/A | — | helper 내부 종료 | spec 적용 외 | GAP-2 |
| L192 | python helper success | N/A | — | helper 내부 종료 | spec 적용 외 | GAP-2 |
| L203 | python helper success | N/A | — | helper 내부 종료 | spec 적용 외 | GAP-2 |
| L208 | python helper success | N/A | — | helper 내부 종료 | spec 적용 외 | GAP-2 |
| L211 | python helper success | N/A | — | helper 내부 종료 | spec 적용 외 | GAP-2 |
| L222 | python helper success | N/A | — | helper 내부 종료 | spec 적용 외 | GAP-2 |
| L282 | fail_fast (knowhow check) | NO | fail_fast | callback 미등록 | R1/R3 위반 | **GAP-3** |
| L321 | fail_fast (design QC loki) | NO | fail_fast | callback 미등록 | R1/R3 위반 | GAP-3 |
| L359 | qc_fail (.qc-result missing) | NO | fail_fast | callback 미등록 | R1/R3 위반 | **GAP-4** |
| L377 | qc_fail (QC FAIL .failed) | NO | fail_fast | **★ task-2707 evidence** · callback 미등록 | R1/R3 위반 | GAP-4 |
| L451 | scope_guard_fail (.escalate) | NO | escalate | callback 미등록 | R1/R3 위반 | **GAP-5** |
| L470 | escalation (.cancelled) | NO | escalate | callback 미등록 | R1/R3 위반 + cancellation 혼재 | GAP-1+5 |
| L483 | taskctl_fail (state_file_missing) | NO | fail_fast | callback 미등록 | R1/R3 위반 | **GAP-6** |
| L492 | taskctl_fail (taskctl status) | NO | fail_fast | callback 미등록 | R1/R3 위반 | GAP-6 |
| L505 | escalation (emit FAIL BLOCKED) | NO | escalate | callback 미등록 + emit failed | R1/R5 위반 | **GAP-7** |
| L510 | success_exit (BLOCKED timer end) | NO | escalate | callback 미등록 (intentional?) | R1/R3 미적용 | GAP-1 |
| L521 | escalation (emit FAIL ESCALATED) | NO | escalate | callback 미등록 + emit failed | R1/R5 위반 | GAP-7 |
| L526 | success_exit (ESCALATED timer end) | NO | escalate | callback 미등록 (intentional?) | R1/R3 미적용 | GAP-1 |
| L531 | fail_fast (pre-push guard) | NO | fail_fast | callback 미등록 | R1/R3 위반 | GAP-3 |
| L535 | fail_fast (qc-check guard) | NO | fail_fast | callback 미등록 | R1/R3 위반 | GAP-3 |
| L571 | fail_fast (PR-GATE open PR) | NO | fail_fast | callback 미등록 | R1/R3 위반 | GAP-3 |
| L616 | fail_fast (GIT-GATE 0 commit) | NO | fail_fast | callback 미등록 | R1/R3 위반 | GAP-3 |
| L707 | scope_guard_fail (fail-closed) | NO | fail_fast | callback 미등록 | R1/R3 위반 | GAP-5 |
| L717 | fail_fast (GIT-GATE empty diff) | NO | fail_fast | callback 미등록 | R1/R3 위반 | GAP-3 |
| L779 | fail_fast (IMPACT-GATE) | NO | fail_fast | callback 미등록 | R1/R3 위반 | GAP-3 |
| L804 | fail_fast (CI-PREFLIGHT) | NO | fail_fast | callback 미등록 | R1/R3 위반 | GAP-3 |
| L976 | fail_fast (G3 FAIL) | NO | fail_fast | callback 미등록 | R1/R3 위반 | GAP-3 |
| L1012 | escalation (G4 ESCALATED) | NO | escalate | callback 미등록 | R1/R3 위반 | GAP-7 |
| L1015 | fail_fast (G4 hard FAIL) | NO | fail_fast | callback 미등록 | R1/R3 위반 | GAP-3 |
| L1052 | fail_fast (LV4-AUDIT no file) | NO | fail_fast | callback 미등록 | R1/R3 위반 | GAP-3 |
| L1088 | fail_fast (LV4-AUDIT no loki) | NO | fail_fast | callback 미등록 | R1/R3 위반 | GAP-3 |
| L1149 | **callback_path (validator FAIL self)** | NO | fail_fast (recursive) | **★ NORMAL-CALLBACK-ENFORCE validator 자체 FAIL → exit 1 + marker 발행** | R5 부분 적용 (★ emit_not_registered_marker 만) · R1/R3 위반 | **GAP-8** |
| L1197 | fail_fast (UNRESOLVED-GATE) | NO | fail_fast | callback 미등록 | R1/R3 위반 | GAP-3 |
| L1255 | fail_fast (GOAL-GATE) | NO | fail_fast | callback 미등록 | R1/R3 위반 | GAP-3 |
| L1290 | fail_fast (GUARD .done block) | NO | fail_fast | callback 미등록 | R1/R3 위반 | GAP-3 |
| L1373 | dirty_workspace_fail (stash audit) | NO | fail_fast | callback 미등록 | R1/R3 위반 | **GAP-9** |
| L1512 | dirty_workspace_fail (destructive) | NO | fail_fast | callback 미등록 | R1/R3 위반 | GAP-9 |

### 2.1 reachability 집계

- **callback_reachable=YES**: **0건** (★ 41 exit 모두 callback gate 미도달)
- **callback_reachable=N/A (nested helper)**: 8건 (L117/L132/L161/L192/L203/L208/L211/L222)
- **callback_reachable=NO**: 33건 (★ 모든 main bash exit)

---

## 3. STEP 3 — R1~R5 4분류 판정

| rule | 문장 (회장 verbatim) | 판정 | evidence |
|---|---|---|---|
| **R1** | callback registration 은 fail-fast 보다 먼저 보장되어야 한다 | **NOT_COVERED** | ★ L1106 / L1532 둘 다 fail-fast 이후 위치 · 29 fail_fast exit 1 path 가 모두 L1106 이전 |
| **R2** | fail-fast 직전이라도 envelope 이 있으면 ANU callback cron 등록은 1회 보장 | **NOT_COVERED** | ★ task-2707 evidence: envelope (800 bytes) 존재 + L377 exit 1 → L1532 helper launch 미실행 · envelope 존재 ≠ 등록 보장 |
| **R3** | 모든 종료 path 에서 callback registration 단계를 결정성 1회 통과 | **NOT_COVERED** | ★ 41 exit 중 callback gate 도달 0건 · success path 4건도 callback 단계 통과 0 (★ L510/L526 BLOCKED/ESCALATED 도 callback gate 우회) |
| **R4** | callback registration 은 idempotent 해야 한다 | **NOT_COVERED** | ★ L1550 helper launch 단일 호출 · idempotency key 또는 duplicate-check 코드 0건 · cron_id 패턴 `task_id::callback_kind` 부분 적용은 task-2626 callback-launch.json verbatim 정합 (★ partial evidence only) |
| **R5** | callback registration 실패는 별도 marker 로 남겨야 한다 | **PARTIALLY_COVERED** | ★ L1130-1149 `emit_not_registered_marker` 가 `{task_id}.normal-callback-not-registered.json` 발행 · 단 L1106-1149 block 내부에서만 작동 · 29 fail_fast exit 1 path 중 28건은 L1106 이전 → marker 미발행 (★ task-2707 evidence: .normal-callback-not-registered.json 부재) |

### 3.1 종합 판정

- R1~R4: **NOT_COVERED** (★ 4건)
- R5: **PARTIALLY_COVERED** (★ 1건)
- FULLY_COVERED 0건 / PARTIALLY_COVERED 1건 / NOT_COVERED 4건 / UNKNOWN 0건

---

## 4. STEP 4 — P2-A 구현 전 추가 필요 contract rule

### 4.1 9 GAP 식별 (★ STEP 2 매트릭스 기반)

| GAP | candidate_rule (★ 규칙 문장만) | reason | affected_exit_paths |
|---|---|---|---|
| **GAP-1** | cancellation path (`.cancelled` marker 존재 / `task-timer end CANCELLED/BLOCKED/ESCALATED`) 에서도 callback registration 의도 명시 또는 명시적 SKIP 명세 필요 | L67/L80/L470/L510/L526 = success exit 또는 cancellation/escalation 경유 · callback 의도 모호 (intentional skip vs accidental skip 분리 0) | L67, L80, L470, L510, L526 (★ 5건) |
| **GAP-2** | python helper 내부 `sys.exit()` (L117~L222) 은 main bash control flow 아님 · spec 적용 대상에서 명시적 제외 필요 (★ "nested helper" 범위 정의) | 본 8건은 helper subprocess 내부 exit · 메인 finish-task.sh control flow 영향 0 · spec drafting 시 의도치 않은 적용 회피 | L117, L132, L161, L192, L203, L208, L211, L222 (★ 8건) |
| **GAP-3** | generic fail_fast (knowhow check / pre-push guard / qc-check guard / PR-GATE / GIT-GATE / IMPACT-GATE / CI-PREFLIGHT / G3/G4 / LV4-AUDIT / UNRESOLVED-GATE / GOAL-GATE / GUARD .done block) 에서 callback registration 결정성 통과 spec 필요 | 16건 = 가장 많은 fail_fast 경로 · R1/R3 가 cover 하려는 핵심 대상 · 단 "callback registration" 의 의미가 (a) bot self-register 검증 통과 인지 (b) finish-task 가 직접 register 인지 spec 모호 | L282, L321, L531, L535, L571, L616, L717, L779, L804, L976, L1015, L1052, L1088, L1197, L1255, L1290 (★ 16건) |
| **GAP-4** | qc_fail (L359/L377) 에서 callback kind 분류 명시 필요 — `normal` 인지 `fail_fast` 인지 `escalate` 인지 | task-2707 evidence: QC FAIL → callback 미등록 · QC FAIL 자체가 ANU 회수 trigger 가 되어야 하는지 doctrine 결정 영역 | L359, L377 (★ 2건) |
| **GAP-5** | scope_guard_fail (L451/L707) 에서 callback kind = `escalate` 강제 + .escalate marker + callback marker 동시 발행 spec 필요 | scope-guard FAIL = task-2569 lineage · P1-B C2 caveat lineage · 회수 무관 ANU 보고 trigger | L451, L707 (★ 2건) |
| **GAP-6** | taskctl_fail (L483/L492) = system 차원 fail · callback kind 분류 모호 | state_file_missing / taskctl status fail 은 인프라 fail · ANU 회수가 무의미할 수 있음 (★ 회장 결정 영역) | L483, L492 (★ 2건) |
| **GAP-7** | escalation path 의 emit_fail (L505/L521) + G4 ESCALATED (L1012) 에서 callback marker 가 escalation marker 와 별도 발행되어야 하는지 명시 필요 | 본 3건은 escalation_marker 자체 emit 실패 또는 G4 escalation · callback marker / escalation marker 양 trail 분리 · spec 모호 | L505, L521, L1012 (★ 3건) |
| **GAP-8** | **callback_path 자체 fail (L1149)** = Layer 1 validator fail · recursion 회피 spec 필요 (★ Layer 2 fallback 트리거 여부 / Layer 1 ↔ Layer 2 분리 강제) | L1149 = NORMAL-CALLBACK-ENFORCE validator 자체 FAIL · emit_not_registered_marker 호출 후 exit 1 · Layer 2 fallback 호출 0 (★ 의도 / 사고 분리 명시 필요) | L1149 (★ 1건) |
| **GAP-9** | dirty_workspace_fail (L1373/L1512) 에서 stash-lifecycle infra fail 처리 분류 명시 필요 | stash audit / destructive action 실패는 워크스페이스 상태 corruption · callback kind = `escalate` 또는 별도 kind 필요 | L1373, L1512 (★ 2건) |

### 4.2 9 candidate contract rule 박제 (★ 신규 · 규칙 문장만 · 구현안 0)

| candidate ID | 규칙 문장 | reason | affected_exit_paths_count |
|---|---|---|---|
| **R6** | `.cancelled` / BLOCKED / ESCALATED success exit 0 경로에서 callback registration 의도를 (a) SKIP 명시 또는 (b) register 강제 둘 중 하나로 명시해야 한다 | GAP-1 — intentional vs accidental skip 분리 0 | 5 |
| **R7** | python helper 내부 `sys.exit()` 는 main control flow 외부로 spec 적용 대상에서 명시적 제외해야 한다 | GAP-2 — nested helper 적용 회피 | 8 |
| **R8** | callback registration 의 enforcement 위치는 (a) Layer 1 validator (bot self-register 검증) (b) Layer 2 registrar (finish-task 직접 register) 둘을 명확히 분리하고 각 layer 의 enforcement 시점을 spec 에 명시해야 한다 | GAP-3 + GAP-8 — Layer 1 / Layer 2 분리 모호 + recursion 위험 | 16 + 1 = 17 |
| **R9** | qc_fail / scope_guard_fail / taskctl_fail / dirty_workspace_fail / escalation_path / generic fail_fast 각 분류마다 callback kind (normal / fail_fast / escalate / hold_for_chair / verifier_result / fallback) 매핑을 명시해야 한다 | GAP-4 + GAP-5 + GAP-6 + GAP-7 + GAP-9 — kind 분류 모호 | 11 |
| **R10** | callback_path 자체 fail (Layer 1 validator self-fail) 시 recursion 회피 spec 명시 + Layer 2 fallback 트리거 여부 결정 | GAP-8 — Layer 1 self-fail recursion 위험 | 1 |
| **R11** | callback registration 의 "결정성 1회 통과" 정의를 (a) 시도 횟수 (b) 시간 한계 (c) idempotency key 패턴 셋 모두로 spec 에 명시해야 한다 | R3/R4 PARTIALLY_COVERED 보완 | 41 (★ 전체) |
| **R12** | spec 의 task_type 7종 (code/system_hook/local_runtime/formalization_commit_only/read_only/callback_only/closeout_marker_only) × 9 exit 분류 = 63 cell 매트릭스 작성 spec 필요 (★ P1-B 192-cell matrix 정합 패턴) | task_type 별 callback kind 매핑 모호 | 전체 |
| **R13** | spec 적용 범위 (in_scope) 와 명시적 적용 제외 (out_of_scope · python helper / nested subprocess / system task) 분리 spec 필요 | GAP-2 + spec drafting boundary 모호 | 8 (helper) + N (system task) |
| **R14** | callback marker 와 escalation marker 가 동일 exit point 에서 발행될 때 양 marker 의 우선순위 + 동시 발행 가능 여부 spec 필요 | GAP-7 — marker trail 분리 모호 | 3 |

### 4.3 신규 9 candidate (R6~R14) 박제 정리

| ID | 한 줄 요약 | priority hint (★ ANU 자체 0 · 회장 결정) |
|---|---|---|
| R6 | cancellation/BLOCKED/ESCALATED SKIP vs register 명시 | (회장 결정) |
| R7 | python helper exit 적용 제외 | (회장 결정) |
| R8 | Layer 1 validator ↔ Layer 2 registrar 분리 | (회장 결정 · GAP-3/8 핵심) |
| R9 | exit 분류 × callback kind 매핑 | (회장 결정 · GAP-4/5/6/7/9 핵심) |
| R10 | Layer 1 self-fail recursion 회피 | (회장 결정) |
| R11 | "결정성 1회 통과" 정의 (시도/시간/idempotency) | (회장 결정 · R3/R4 보완) |
| R12 | task_type 7 × exit 9 = 63 cell 매트릭스 | (회장 결정 · P1-B 정합) |
| R13 | in_scope / out_of_scope 분리 | (회장 결정 · GAP-2) |
| R14 | callback marker / escalation marker 동시 발행 spec | (회장 결정 · GAP-7) |

---

## 5. STEP 5 — 최종 판정

### 5.1 evidence-only 종합

- R1~R5 판정: NOT_COVERED 4건 + PARTIALLY_COVERED 1건 / FULLY_COVERED 0건
- GAP 식별: 9건 (★ GAP-1~9)
- 신규 candidate rule: 9건 (★ R6~R14)
- 41 exit 중 callback_reachable=YES: **0건**
- task-2707 production evidence: 1건 (★ L377 fail-fast → L1532 미도달 · callback 미등록)

### 5.2 hypothesis 분리 (★ evidence 아님)

- **hypothesis 1**: Layer 1 validator (L1106) 의 envelope 존재 check 는 bot self-register doctrine 강제 의도 — finish-task 가 직접 register 하지 않는 것이 의도일 수도 있음 (★ 회장 결정 영역 · evidence 0)
- **hypothesis 2**: GAP-1 의 cancellation/BLOCKED/ESCALATED success exit 0 에서 callback skip 은 의도일 가능성 (task 자체가 cancel 된 경우 ANU 회수 불필요) — 단 spec 명시 0 (★ 회장 결정 영역)
- **hypothesis 3**: 9 candidate rule (R6~R14) 모두 P2-A 구현 전 spec 결정 필요한가 vs 일부는 P2-A 이후 별도 round 가능한가 (★ 회장 결정 영역)

### 5.3 최종 판정

| 판정 | 선택 여부 | 근거 |
|---|---|---|
| **P2-A_IMPLEMENTATION_READY** | REJECTED | ★ R1~R5 4건 NOT_COVERED + 9 GAP 미해소 + 9 candidate rule 신규 필요 → 현재 contract 만으로 P2-A 구현 시 GAP-1~9 의 implicit 결정이 코드에 박힘 (★ doctrine drift 위험) |
| **P2-A_NEEDS_CONTRACT_REFINEMENT** | **SELECTED** | ★ 9 candidate rule 회장 verbatim 결정 후 P2-A 구현 진입 · evidence 정합 |

**verdict = P2-A_NEEDS_CONTRACT_REFINEMENT**

★ ANU 자체 분류 결정 0 — 회장 verbatim 2 enum 중 evidence-only 선택 · 신규 신설 0.

---

## 6. Chair Decision Pending (★ 회장 결정 6 영역)

1. ★ R6~R14 9 candidate rule 채택 여부 (★ 각 rule 별 ACCEPT / REJECT / MODIFY)
2. ★ R8 (Layer 1 validator ↔ Layer 2 registrar 분리) — 현재 두 layer 가 동시 존재하는 의도 / 사고 분리
3. ★ R9 (exit 분류 × callback kind 매핑) — 9 분류 × 6 kind 매핑 회장 결정
4. ★ R12 (task_type 7 × exit 9 = 63 cell matrix) — P1-B 192-cell 패턴 적용 여부
5. ★ P2-A 진입 조건 — R6~R14 중 어디까지 채택되면 P2-A_READY 로 전환
6. ★ task-2707 처리 — P2-A 구현 후 재검증 vs ANU 독립 verifier 대체 vs HOLD_FOR_CHAIR (★ p2 spec §9 lineage 정합)

---

## 7. ANU Doctrine Compliance

- ★ ANU 자체 P2-A 구현 0 (★ 회장 verbatim 'P2-A 구현으로 바로 가지 마라')
- ★ ANU 자체 코드 수정 0 / dispatch 0 / PR 0 / branch push 0 / merge 0
- ★ ANU 자체 분류 결정 0 (★ STEP 3 4 enum · STEP 5 2 enum 모두 evidence-only 매핑)
- ★ ANU 자체 새 candidate rule 작성 = 회장 verbatim "candidate_rule / reason / affected_exit_paths 형식으로만 작성" 정합 · 구현안 0
- ★ hypothesis 분리 박제 (★ 회장 verbatim '추정은 hypothesis 로 분리')
- ★ evidence-only 답변 (★ 회장 verbatim 'evidence 만 사용')

---

## 8. Linked Markers

- `memory/specs/p2_finish_task_callback_before_failfast_contract_draft_260529.md` (★ R1~R5 verbatim 원본)
- `memory/events/anu_p2_finish_task_routing_callback_registration_after_failfast_evidence_260529.json` (★ STEP 1~4 evidence)
- `memory/events/task-2707.implementation-pass-routing-fail-260529.json` (★ 회장 verbatim 분류 고정)
- `memory/events/p1b_finish_task_profile_contract_accepted_with_known_caveats_260529.json` (★ C2 caveat lineage)
- `scripts/finish-task.sh L1106-1160` (★ Layer 1 NORMAL-CALLBACK-ENFORCE validator)
- `scripts/finish-task.sh L1532-1558` (★ Layer 2 callback runtime enforcement gate)
- 41 exit points verbatim source location (★ §1.1 grep evidence)

---

## 9. Sentinel

★ 본 audit = read-only evidence + R1~R5 커버리지 검증. P2-A 구현 0 · 코드 수정 0 · dispatch 0. 끝
