# Phase B 종료 분류 자동화 명세 (8종 enum + 자동 분류 룰)

작성: 2026-05-08
작성자: 모코시 (dev6-team / 페룬)
근거: task-2489 (회장 명시), phase_b_integration_items_260507.md
status: SPEC (production 미반영, 격리)

---

## 1. 개요

### 1.1 8종 분류 enum의 목적

현행 시스템은 task 종료를 DONE / ESCALATED 2종으로만 구분하며, 아래 세 가지 구조적 결함을 내재한다.

1. **stale timer**: 외부 차단으로 대기 중인 task에 retry 룰이 발동되어 무의미한 재작업 시도
2. **마커 모순**: `task-timer.py end`가 close lifecycle 실패 케이스에도 `.done`을 자동 생성하여 `.escalate`와 동시 존재
3. **분류 오판**: 본질 PASS 상태인 task가 외부 의존으로 후속 단계가 막혔을 때 본질 결함으로 오인

8종 분류 enum은 task의 실제 종료 상태를 정확히 명명함으로써 위 결함을 제거하고, dispatch.py가 각 상태에 맞는 후속 액션을 자동으로 선택할 수 있도록 한다.

### 1.2 production 미반영 원칙 (회장 명시)

- 본 명세는 **SPEC 단계**이며 production 시스템에 즉시 반영되지 않는다.
- `task-timer.py`, `dispatch.py`, `scripts/finish-task.sh` 등 production 파일은 수정 금지.
- dry-run classifier (`tools/poc/termination_classifier.py`)를 통해 회귀 fixtures 5개 검증 후 회장이 production 반영 여부를 재결정한다.

### 1.3 dry-run 검증 후 회장 재결정 영역

- task-2486 머지 완료 후 회장이 Phase B 진입 시점을 결정한다.
- 본 명세 + dry-run classifier 회귀 결과를 근거로 아래 4개 결정이 내려진다.
  1. `task-timer.py end --reason` 옵션 추가 여부 및 default 동작 변경 범위
  2. dispatch.py 자동 트리거 도입 범위 (전자동 vs 회장 개입 경계)
  3. 대시보드 7섹션 보드 설계 확정
  4. orchestrator_subagent_prompt 보강 시점

---

## 2. 8종 분류 enum 정의

| 분류 | merge 상태 | 차단 위치 | 본 task 책임 | 정의 | 예시 task | 후속 marker |
|---|---|---|---|---|---|---|
| `DONE` | MERGED | 없음 | — | 본질 PASS + PR merge + lifecycle close 모두 정상 완료 | task-2466, task-2479 | `.done` |
| `ESCALATED` | OPEN 또는 MERGED | 본질 단계 | 있음 | 본질 결함 존재, retry로 본질 재작업 필요 | 본질 결함 케이스 | `.escalate` |
| `DOGFOODING_PENDING` | MERGED 또는 머지 후 | dogfooding layer | 없음 | 본질 PASS + CI PASS이지만, dogfooding layer(자기검증) 외부 의존으로 미완 | task-2481 | `.dogfooding-pending` + `.dogfooding-pending.conditions` |
| `MERGE_PENDING_DEPENDENCY` | OPEN | merge 자체 | 없음 | 본질 PASS 인정되었으나, 후속/외부 task 머지가 선행되어야 본 PR merge 가능 | task-2472+1 | `.merge-pending` + `.merge-pending.conditions` |
| `MERGED_CLOSE_BLOCKED_EXTERNAL` | MERGED | post-merge close | 없음 | PR merge 정상 완료되었으나, close lifecycle(`finish-task.sh`/QC/git_evidence)이 외부 workspace dirty로 실패 | task-2483 | `.close-blocked-external` (`.done` + `.escalate` 보존) |
| `BLOCKED_BY_EXTERNAL_DEPENDENCY` | OPEN 또는 시작 불가 | 본질 진행 시작 | 없음 | 외부 시스템(API/infra/ruleset) 장애로 본질 작업 진행 자체가 차단됨 | API 다운, infra 장애 | `.blocked-external` + `.blocked-external.conditions` |
| `FAILED_PREEXISTING` | OPEN | 본질 진행 | 없음 | 본 task 이전 commit/state의 기존 결함이 본 task 진행을 차단 (본 task 본질 결함 아님) | main lint/test 기존 실패 | `.failed-preexisting` + `.failed-preexisting.conditions` |
| `WAITING_FOR_CHAIR_DECISION` | 다양 | 정책 결정 | 없음 | 방향성/우선순위/예외 승인 회장 판단 대기. 분류기가 분류 불가 상태 포함 | 정책 충돌, 사용자 영향 큰 변경 | `.waiting-chair` + `.waiting-chair.conditions` |

**핵심 구분**
- DONE: 성공적 종료 (끝)
- ESCALATED: 실패적 종료 (끝, 단 retry 의미 있음)
- 나머지 6종: 조건부 대기 (끝이 아님, 외부 조건 충족 필요, retry 무의미)

---

## 3. 발동 조건 (boolean expression 형식)

### 3.1 DONE

```yaml
classification: DONE
trigger:
  ALL:
    - pr_merged_at IS NOT NULL                       # PR이 실제로 merge 완료
    - close_lifecycle_state == "CLEAN"               # finish-task.sh / QC / git_evidence 모두 PASS
    - essence_verdict == "PASS"                      # 본질 작업 판정 PASS
    - workspace_dirty_owner IS NULL                  # 외부 dirty 없음
evidence_inputs:
  - pr_state                    # "MERGED"
  - pr_merged_at                # not null
  - ci_rollup                   # all required checks PASS
  - close_lifecycle_state       # "CLEAN"
  - essence_verdict             # "PASS"
  - workspace_dirty_owner       # null
```

### 3.2 ESCALATED

```yaml
classification: ESCALATED
trigger:
  ALL:
    - essence_verdict == "FAIL"                      # 본질 결함 확인
    - retry_count >= retry_threshold                 # retry 임계 초과 (기본값 3회)
  ANY:
    - pr_state == "OPEN"                             # PR 미머지 (본질 결함으로 막힘)
    - pr_state == "MERGED" AND close_lifecycle_state == "FAIL" AND essence_verdict == "FAIL"
evidence_inputs:
  - pr_state
  - pr_merged_at
  - retry_count
  - ci_rollup
  - essence_verdict             # "FAIL"
  - close_lifecycle_state
```

### 3.3 DOGFOODING_PENDING

```yaml
classification: DOGFOODING_PENDING
trigger:
  ALL:
    - essence_verdict == "PASS"                      # 본질 코드 구현 PASS
    - ci_rollup == "PASS"                            # CI required check 100% PASS
    - qc_result IN ["PASS", "WARN"]                  # qc_result FAIL 0건 (WARN 이하 허용)
    - dogfooding_blocker_owner == "EXTERNAL"         # dogfooding layer 차단 사유가 외부 의존
  ANY:
    - dogfooding_blocker_type IN ["BOT_TOKEN_401", "RULESET_DEPENDENCY", "SELF_APPROVAL_BLOCKED"]
evidence_inputs:
  - pr_state                    # "MERGED" 또는 머지 후 상태
  - pr_merged_at                # not null
  - ci_rollup                   # "PASS"
  - essence_verdict             # "PASS"
  - qc_result                   # "PASS" 또는 "WARN"
  - dogfooding_blocker_owner    # "EXTERNAL"
  - dogfooding_blocker_type     # BOT_TOKEN_401 / RULESET_DEPENDENCY / SELF_APPROVAL_BLOCKED
```

### 3.4 MERGE_PENDING_DEPENDENCY

```yaml
classification: MERGE_PENDING_DEPENDENCY
trigger:
  ALL:
    - essence_verdict == "PASS"                      # 본질 PASS 명문화 (followup.txt 또는 회장 발화)
    - pr_state == "OPEN"                             # PR 아직 OPEN (미머지)
    - pr_merged_at IS NULL                           # merge 미완
    - ci_fail_owner == "EXTERNAL_DEPENDENCY"         # CI fail 사유가 외부 dependency (본질 결함 아님)
    - dependency_task_id IS NOT NULL                 # 후속 의존 task id 명시
    - done_marker_exists == false                    # .done 없음
  ANY:
    - retry_count >= retry_threshold                 # retry 초과 (보조 신호, 본질 결함 아님 확인 필수)
    - stale_timer_detected == true                   # 무진척 stale timer 감지
evidence_inputs:
  - pr_state                    # "OPEN"
  - pr_merged_at                # null
  - essence_verdict             # "PASS"
  - ci_rollup                   # FAIL (외부 의존)
  - ci_fail_owner               # "EXTERNAL_DEPENDENCY"
  - dependency_task_id          # 후속 task id
  - done_marker_exists          # false
  - retry_count
```

### 3.5 MERGED_CLOSE_BLOCKED_EXTERNAL

```yaml
classification: MERGED_CLOSE_BLOCKED_EXTERNAL
trigger:
  ALL:
    - pr_merged_at IS NOT NULL                       # PR merge 완료
    - pr_state == "MERGED"                           # merge 상태 확인
    - admin_override == false                        # admin override 0건
    - ruleset_bypass == false                        # ruleset bypass 0건
    - forbidden_violations == 0                      # forbidden 위반 0건
    - close_lifecycle_state == "FAIL"                # finish-task.sh / QC / git_evidence 실패
    - workspace_dirty_owner == "EXTERNAL"            # 차단 사유가 외부 workspace dirty
  ANY:
    - retry_count >= retry_threshold                 # retry 초과 (escalate 박제됐으나 본질 재작업 무의미)
evidence_inputs:
  - pr_state                    # "MERGED"
  - pr_merged_at                # not null
  - close_lifecycle_state       # "FAIL"
  - workspace_dirty_owner       # "EXTERNAL" (타 task 영역 / 운영 복사본 / 시스템 활동 파일)
  - admin_override              # false
  - ruleset_bypass              # false
  - forbidden_violations        # 0
  - retry_count
```

### 3.6 BLOCKED_BY_EXTERNAL_DEPENDENCY

```yaml
classification: BLOCKED_BY_EXTERNAL_DEPENDENCY
trigger:
  ALL:
    - pr_state == "OPEN"                             # PR 미머지
    - essence_verdict IN ["UNKNOWN", "BLOCKED"]      # 본질 시작 자체가 차단됨
    - external_system_blocker == true                # 외부 시스템(API/infra/ruleset) 차단 확인
    - workspace_dirty_owner IS NULL                  # 외부 workspace dirty와 별개 (시스템 장애)
  ANY:
    - blocker_type IN ["API_DOWN", "INFRA_OUTAGE", "RULESET_BLOCKED", "EXTERNAL_SERVICE_FAIL"]
evidence_inputs:
  - pr_state                    # "OPEN"
  - essence_verdict             # "UNKNOWN" 또는 "BLOCKED"
  - external_system_blocker     # true
  - blocker_type                # API_DOWN / INFRA_OUTAGE / RULESET_BLOCKED 등
  - workspace_dirty_owner       # null (외부 시스템 장애와 구분)
```

### 3.7 FAILED_PREEXISTING

```yaml
classification: FAILED_PREEXISTING
trigger:
  ALL:
    - pr_state == "OPEN"                             # PR 미머지
    - essence_verdict IN ["UNKNOWN", "BLOCKED"]      # 본질 진행 불가
    - preexisting_failure_confirmed == true          # 실패 원인이 본 task 이전 commit/state임을 확인
    - preexisting_failure_scope == "EXTERNAL"        # 기존 결함이 본 task 코드 범위 밖
  ANY:
    - ci_fail_type IN ["LINT_PREEXISTING", "TEST_PREEXISTING", "MAIN_DIRTY"]
evidence_inputs:
  - pr_state                    # "OPEN"
  - essence_verdict             # "UNKNOWN" 또는 "BLOCKED"
  - preexisting_failure_confirmed   # true
  - preexisting_failure_scope   # "EXTERNAL"
  - ci_fail_type                # LINT_PREEXISTING / TEST_PREEXISTING 등
  - dependency_task_id          # 기존 결함 관련 task (있을 경우)
```

### 3.8 WAITING_FOR_CHAIR_DECISION

```yaml
classification: WAITING_FOR_CHAIR_DECISION
trigger:
  ALL:
    - chair_decision_required == true                # 방향성/예외/승인 회장 판단 명시적 필요
  ANY:
    - policy_conflict_detected == true               # 정책 충돌 감지
    - escalation_path_ambiguous == true              # 분류기가 상위 분류에 매칭 실패
    - user_impact_high == true                       # 사용자 영향 큰 변경으로 회장 승인 필요
    - unclassified_after_priority_eval == true       # 우선순위 평가 후 미매칭 (UNCLASSIFIED 대체)
evidence_inputs:
  - pr_state                    # 다양
  - essence_verdict             # 다양
  - ci_rollup                   # 다양
  - chair_decision_required     # true
  - policy_conflict_detected    # true/false
  - escalation_path_ambiguous   # true/false
```

---

## 4. 우선순위 + 충돌 해결 룰

### 4.1 평가 순서 (decision tree)

분류기는 아래 순서대로 조건을 평가하며, 가장 먼저 ALL 조건을 만족하는 분류를 채택한다.

```
1. PR MERGED + close_lifecycle_state == CLEAN
   → DONE

2. PR MERGED + close_lifecycle_state == FAIL + workspace_dirty_owner == EXTERNAL
   → MERGED_CLOSE_BLOCKED_EXTERNAL

3. PR MERGED + dogfooding_blocker_owner == EXTERNAL + essence_verdict == PASS
   → DOGFOODING_PENDING

4. PR OPEN + essence_verdict == PASS + ci_fail_owner == EXTERNAL_DEPENDENCY + dependency_task_id IS NOT NULL
   → MERGE_PENDING_DEPENDENCY

5. PR OPEN + essence_verdict IN [UNKNOWN, BLOCKED] + external_system_blocker == true
   → BLOCKED_BY_EXTERNAL_DEPENDENCY

6. PR OPEN + essence_verdict IN [UNKNOWN, BLOCKED] + preexisting_failure_confirmed == true
   → FAILED_PREEXISTING

7. chair_decision_required == true
   → WAITING_FOR_CHAIR_DECISION

8. essence_verdict == FAIL + retry_count >= retry_threshold
   → ESCALATED

9. 위 1~8 미해당
   → UNCLASSIFIED (분류 보류, 회장 알림)
```

### 4.2 동시 매칭 충돌 해결

- 두 개 이상의 분류 조건이 동시에 만족된 경우: **가장 낮은 번호(높은 우선순위)** 채택
- 예외: 아래 충돌 쌍은 별도 규칙 적용

| 충돌 쌍 | 해결 규칙 |
|---|---|
| DONE vs MERGED_CLOSE_BLOCKED_EXTERNAL | close_lifecycle_state 값으로 결정. CLEAN → DONE, FAIL → MERGED_CLOSE_BLOCKED_EXTERNAL |
| ESCALATED vs MERGE_PENDING_DEPENDENCY | essence_verdict로 결정. FAIL → ESCALATED, PASS → MERGE_PENDING_DEPENDENCY |
| ESCALATED vs DOGFOODING_PENDING | essence_verdict + ci_rollup으로 결정. essence FAIL → ESCALATED, essence PASS + ci PASS → DOGFOODING_PENDING |
| BLOCKED_BY_EXTERNAL_DEPENDENCY vs FAILED_PREEXISTING | blocker 원인으로 결정. 외부 시스템 장애 → BLOCKED_BY_EXTERNAL_DEPENDENCY, 이전 commit 결함 → FAILED_PREEXISTING |

### 4.3 신뢰도(confidence) 산출 룰

| 조건 만족 상태 | confidence 값 |
|---|---|
| 해당 분류의 ALL 조건 전부 만족 + ANY 조건 1개 이상 만족 | 1.0 |
| 해당 분류의 ALL 조건 전부 만족 + ANY 조건 0개 | 0.8 |
| 해당 분류의 ALL 조건 일부 미충족 + ANY 조건 만족 | 0.5 ~ 0.7 |
| 우선순위 평가 중 복수 분류에 걸치는 경계 케이스 | 0.4 이하 → WAITING_FOR_CHAIR_DECISION 강제 |

confidence 임계값 기준: Phase B 운영 데이터 수집 후 회장 재결정 (현재 미정).

---

## 5. 마커 파일 컨벤션

### 5.1 분류별 마커 파일 표

| 분류 | 마커 파일명 | 부가 파일 | 차단 동작 |
|---|---|---|---|
| `DONE` | `.done` | — | 정상 close 처리, 알림 종료 |
| `ESCALATED` | `.escalate` | — | retry 룰 발동 (max 3), 초과 시 회장 알림 |
| `DOGFOODING_PENDING` | `.dogfooding-pending` | `.dogfooding-pending.conditions` | 외부 의존 복구 대기, `.done` 미발행 |
| `MERGE_PENDING_DEPENDENCY` | `.merge-pending` | `.merge-pending.conditions` | 후속 task `.done` 대기, `.done` 미발행 |
| `MERGED_CLOSE_BLOCKED_EXTERNAL` | `.close-blocked-external` | `.done` + `.escalate` 보존 | hygiene cleanup task 발행 제안 |
| `BLOCKED_BY_EXTERNAL_DEPENDENCY` | `.blocked-external` | `.blocked-external.conditions` | 외부 시스템 복구 대기, `.done` 미발행 |
| `FAILED_PREEXISTING` | `.failed-preexisting` | `.failed-preexisting.conditions` | 별도 기존 결함 fix task 발행 제안, `.done` 미발행 |
| `WAITING_FOR_CHAIR_DECISION` | `.waiting-chair` | `.waiting-chair.conditions` | 회장 결정 발화 대기, `.done` 미발행 |

### 5.2 마커 파일 멱등성 규칙

분류기 또는 dispatch.py가 재실행될 때 마커 파일의 중복 생성을 방지한다.

```yaml
idempotency_rules:
  - rule: "마커 파일이 이미 존재하면 생성 skip, 내용 덮어쓰기 금지"
  - rule: ".done과 .escalate가 동시 존재하는 경우 MERGED_CLOSE_BLOCKED_EXTERNAL 분류 우선 적용"
  - rule: ".close-blocked-external.resolved 존재 시 MERGED_CLOSE_BLOCKED_EXTERNAL 해소 완료로 판정"
  - rule: "재실행 시 기존 .conditions 파일 보존, 새 조건 append만 허용 (overwrite 금지)"
  - rule: "ESCALATED → DOGFOODING_PENDING 재분류 시: 기존 .escalate → .escalate.reclassified 로 rename"
```

### 5.3 .conditions 파일 표준 구조

각 `.conditions` 파일은 아래 YAML 형식을 따른다.

```yaml
# 예시: .merge-pending.conditions
classification: MERGE_PENDING_DEPENDENCY
created_at: <ISO 8601>
task_id: <task_id>
essence_verdict: PASS
block_reason: "외부 후속 task 의존"
dependency_task_id: <후속 task id>
conditions:
  - id: 1
    description: "의존 task .done 마커 생성 확인"
    status: PENDING
  - id: 2
    description: "의존 task .done 감지 후 본 PR CI rerun 발행"
    status: PENDING
  - id: 3
    description: "CI required check 100% PASS"
    status: PENDING
  - id: 4
    description: "guard PASS 확인"
    status: PENDING
  - id: 5
    description: "PR MERGED 확인"
    status: PENDING
  - id: 6
    description: ".merge-pending → .done 자동 변환"
    status: PENDING
forbidden:
  - ".done 선행 발행"
  - "admin override merge"
  - "본질 코드 추가 수정"
```

---

## 6. 자동 트리거 매트릭스 (분류 → 후속 액션)

| 분류 | 자동 액션 | dispatch.py 트리거 조건 | 사람 개입 필요 여부 |
|---|---|---|---|
| `DONE` | `.done` 생성 → lifecycle close → 알림 종료 | close_lifecycle_state == CLEAN | 불필요 |
| `ESCALATED` | `.escalate` 발행 → retry 룰 발동 (max 3) → 초과 시 회장 알림 이벤트 발행 | retry_count >= threshold AND essence_verdict == FAIL | retry 초과 시 회장 판단 필요 |
| `DOGFOODING_PENDING` | `.dogfooding-pending` + `.conditions` 발행 → 인프라 복구 task `.done` 감지 polling → dogfooding 재시도 propose | 인프라 복구 task `.done` 감지 | 재시도 실행은 회장 승인 후 |
| `MERGE_PENDING_DEPENDENCY` | `.merge-pending` + `.conditions` 발행 → 의존 task `.done` 감지 → CI rerun 발행 → `.merge-pending` → `.done` 자동 변환 | dependency_task_id `.done` 마커 생성 감지 | 불필요 (전자동, 단 guard PASS 확인 필요) |
| `MERGED_CLOSE_BLOCKED_EXTERNAL` | `.close-blocked-external` 발행 (`.done` + `.escalate` 보존) → hygiene cleanup task 발행 propose → cleanup `.done` 감지 후 `finish-task.sh` 재실행 제안 | cleanup task `.done` 감지 | cleanup task 실행은 사람 검토 후 |
| `BLOCKED_BY_EXTERNAL_DEPENDENCY` | `.blocked-external` + `.conditions` 발행 → 외부 시스템 헬스체크 polling → 복구 감지 시 재진입 propose | 외부 시스템 healthcheck PASS 감지 | 재진입 실행은 회장 승인 후 |
| `FAILED_PREEXISTING` | `.failed-preexisting` + `.conditions` 발행 → 기존 결함 fix task 발행 propose → fix task `.done` 감지 시 본 task 영향 확인 propose | fix task `.done` 감지 | fix task 발행 및 재시도 회장 승인 후 |
| `WAITING_FOR_CHAIR_DECISION` | `.waiting-chair` + `.conditions` 발행 → 회장 발화 감지 polling → 발화 감지 시 분류 재산정 트리거 | 회장 발화 키워드 감지 | 회장 결정 필수 |

---

## 7. task-timer.py `--reason` 옵션 명세 (문서만, 코드 X)

### 7.1 시그니처

```
python3 memory/task-timer.py end <task_id> [--reason <classification>]
```

### 7.2 분류별 동작 명세

| `--reason` 값 | 발행 마커 | `.done` 발행 여부 | 비고 |
|---|---|---|---|
| `done` (default 후순위) | `.done` | O | 정상 종료 |
| `escalated` | `.escalate` | X | `.done` 미발행 |
| `dogfooding_pending` | `.dogfooding-pending` | X | `.done` 미발행 |
| `merge_pending_dependency` | `.merge-pending` | X | `.done` 미발행 |
| `merged_close_blocked_external` | `.close-blocked-external` | O (보존) | 기존 `.done` + `.escalate` 보존, 신규 `.done` 생성 금지 |
| `blocked_external` | `.blocked-external` | X | `.done` 미발행 |
| `failed_preexisting` | `.failed-preexisting` | X | `.done` 미발행 |
| `waiting_chair` | `.waiting-chair` | X | `.done` 미발행 |

### 7.3 default 동작 변경 명시

현행 결함: `--reason` 미지정 시 모든 종료에 `.done` 자동 생성 → MERGE_PENDING_DEPENDENCY 케이스에 회장 금지 #1 위반.

Phase B 진입 시 변경 사항:
- `--reason` 인자 없이 `task-timer.py end <task_id>` 호출 시 `.done`을 **자동 생성하지 않고** 호출자에게 `--reason` 명시를 요구하는 오류 메시지 출력
- 기존 `done` 동작을 원할 경우 명시적으로 `--reason done` 지정 필요
- 호환성: `--reason done`은 현행 기본 동작과 동일하게 유지

### 7.4 주의사항

**이 섹션은 문서일 뿐. task-timer.py production 수정 절대 금지 (회장 명시).**

---

## 8. 회귀 fixtures 매핑 (5개)

### 8.1 매핑 표

| fixture task | 기대 분류 | 판정 근거 요약 |
|---|---|---|
| task-2466 | `DONE` | PR MERGED + close lifecycle CLEAN + essence PASS |
| task-2481 | `DOGFOODING_PENDING` | PR MERGED + essence PASS + CI PASS + dogfooding layer 외부 의존 (BOT_TOKEN_401) |
| task-2472+1 | `MERGE_PENDING_DEPENDENCY` | PR OPEN + essence PASS + CI FAIL(외부 의존) + dependency_task_id=task-2472+2 |
| task-2483 | `MERGED_CLOSE_BLOCKED_EXTERNAL` | PR MERGED + close lifecycle FAIL + workspace_dirty_owner=EXTERNAL |
| task-2485 | `MERGE_PENDING_DEPENDENCY` | CODE_PASS 판정 + PR OPEN + 후속 의존 구조 편입 |

### 8.2 fixture별 evidence input 상세

#### task-2466 → DONE

```yaml
task_id: task-2466
expected_classification: DONE
evidence:
  pr_state: MERGED
  pr_merged_at: "not null"
  ci_rollup: PASS
  close_lifecycle_state: CLEAN
  essence_verdict: PASS
  retry_count: 0
  workspace_dirty_owner: null
  dependency_task_id: null
confidence: 1.0
```

#### task-2481 → DOGFOODING_PENDING

```yaml
task_id: task-2481
expected_classification: DOGFOODING_PENDING
evidence:
  pr_state: MERGED
  pr_merged_at: "not null"
  ci_rollup: PASS
  essence_verdict: PASS
  qc_result: WARN
  dogfooding_blocker_owner: EXTERNAL
  dogfooding_blocker_type: BOT_TOKEN_401
  close_lifecycle_state: INCOMPLETE  # dogfooding layer 미완
  retry_count: 3
  dependency_task_id: null           # 인프라 복구 task로 연결
confidence: 1.0
notes: "bot-authored merge flow + BOT_TOKEN graphql 401 + 사람 approval 의존. 초기 ESCALATED 오분류 → DOGFOODING_PENDING 재분류 사례."
```

#### task-2472+1 → MERGE_PENDING_DEPENDENCY

```yaml
task_id: task-2472+1
expected_classification: MERGE_PENDING_DEPENDENCY
evidence:
  pr_state: OPEN
  pr_merged_at: null
  ci_rollup: FAIL
  ci_fail_owner: EXTERNAL_DEPENDENCY  # workflow regex task-[0-9]+ 미지원
  essence_verdict: PASS               # 회장 인정 2026-05-07T22:25
  qc_result: PASS
  retry_count: 4                      # threshold 초과 (보조 신호)
  done_marker_exists: false
  dependency_task_id: task-2472+2    # workflow regex fix
  stale_timer_detected: true          # 4시간 stale
confidence: 1.0
notes: "taskctl reconcile 신설. CI FAIL 원인: workflow regex가 task-N+M 형식 미지원 → task-2472로 잘라 scope 오인식. 초기 ESCALATED stale 4시간 후 재분류."
```

#### task-2483 → MERGED_CLOSE_BLOCKED_EXTERNAL

```yaml
task_id: task-2483
expected_classification: MERGED_CLOSE_BLOCKED_EXTERNAL
evidence:
  pr_state: MERGED
  pr_merged_at: "2026-05-07T13:54:33Z"  # commit 37e26ed4
  ci_rollup: PASS
  essence_verdict: PASS
  admin_override: false
  ruleset_bypass: false
  forbidden_violations: 0
  close_lifecycle_state: FAIL
  workspace_dirty_owner: EXTERNAL  # dev1 task-2479 영역 + systemd 운영 복사본 + 시스템 활동 파일
  retry_count: 3
  dependency_task_id: task-2484   # hygiene cleanup
confidence: 1.0
notes: "refresh_bot_token.py 신설 PR #45 정상 머지. git_evidence NO_UNCOMMITTED FAIL: 4 unstaged + 9 staged. 차단 owner=외부. task-2484 cleanup 후 finish-task.sh 재실행 예정."
```

#### task-2485 → MERGE_PENDING_DEPENDENCY (CODE_PASS 편입)

```yaml
task_id: task-2485
expected_classification: MERGE_PENDING_DEPENDENCY
evidence:
  pr_state: OPEN
  pr_merged_at: null
  essence_verdict: PASS               # CODE_PASS 판정
  ci_rollup: FAIL                     # 외부 의존 (후속 task 의존)
  ci_fail_owner: EXTERNAL_DEPENDENCY
  done_marker_exists: false
  dependency_task_id: "후속 의존 task"
  retry_count: 0
confidence: 0.8
notes: "CODE_PASS 판정으로 본질 PASS 인정. MERGE_PENDING_DEPENDENCY 분류 체계 편입 사례. dependency_task_id 명확화 필요 (confidence 0.8)."
```

> **비고**: task-2485는 정확한 evidence가 미정의되어 가정 시나리오로 작성. Phase B 진입 시 실증 데이터로 교체 또는 회귀 세트에서 제외 결정 필요. (Codex 검증 medium 지적)
>
> **회귀 신뢰도: 낮음 (가정 시나리오)**

---

## 9. Production 반영 결정 (회장 재결정 영역)

### 9.0 UNCLASSIFIED enum 운용 방안 (dry-run 안전망, production 반영 시 회장 재결정)

#### enum 성격 구분

- **8종 분류 enum** (`DONE`, `ESCALATED`, `DOGFOODING_PENDING`, `MERGE_PENDING_DEPENDENCY`, `MERGED_CLOSE_BLOCKED_EXTERNAL`, `BLOCKED_BY_EXTERNAL_DEPENDENCY`, `FAILED_PREEXISTING`, `WAITING_FOR_CHAIR_DECISION`): **production 출력 enum**. downstream 소비자(dispatch.py, 대시보드 등)가 소비하는 공식 분류값.
- **`UNCLASSIFIED`**: **dry-run 안전망 enum**. 우선순위 1~8 평가 후 미해당 케이스를 회장 알림으로 보내기 위한 표현 수단. production에서는 8종에 포함되지 않는 별도 처리 경로.

#### production 반영 시 선택지 (회장 재결정)

본 명세 + dry-run 검증 완료 후 아래 두 가지 방안 중 회장이 결정:

- **(A) UNCLASSIFIED → WAITING_FOR_CHAIR_DECISION 흡수**: UNCLASSIFIED 케이스 발생 시 자동으로 WAITING_FOR_CHAIR_DECISION으로 격상하여 회장 알림 처리. production enum은 8종 유지.
- **(B) UNCLASSIFIED 별도 보존**: UNCLASSIFIED를 9번째 enum으로 production에 편입하고, 운영팀 manual 분류 큐에 적재. downstream 소비자가 UNCLASSIFIED를 처리할 수 있도록 인터페이스 확장 필요.

> **결정 시점**: 본 명세 확정 + dry-run classifier 회귀 fixtures 5개 PASS 확인 후 회장 재결정. 현재 방안 미결.

### 9.1 반영 전제 조건

- task-2486 머지 완료 후 회장이 Phase B 진입 시점을 별도 명시
- 본 명세 (`phase_b_termination_classifier_spec_260508.md`) 확정
- dry-run classifier (`tools/poc/termination_classifier.py`) 회귀 fixtures 5개 전부 PASS 확인

### 9.2 반영 단계 및 회장 승인 게이트

```
단계 1: task-timer.py `--reason` 옵션 추가
  - 범위: `--reason` 파라미터 파싱 + 분류별 마커 발행 로직
  - 게이트: 회장 승인 (운영 파일이므로 별도 PR + 검토)
  - 선행 조건: dry-run classifier 회귀 PASS

단계 2: dispatch.py 자동 트리거 도입
  - 범위: 분류별 후속 액션 자동화 (섹션 6 매트릭스 기준)
  - 게이트: 회장 승인 (자동 트리거 범위 확정 필요)
  - 선행 조건: 단계 1 완료 + 운영 환경 1주 안정 확인

단계 3: 대시보드 7섹션 통합
  - 범위: 종료 분류별 보드 (DONE 외 6종 별도 섹션) + 후속 조건 진척도 + 의존 task 시각적 연결
  - 게이트: 회장 승인 (UX 확정 필요)
  - 선행 조건: 단계 2 완료 + 분류 enum 데이터 충분 적재
```

### 9.3 롤백 정책

- 각 단계에서 예상치 못한 분류 오동작 발생 시 즉시 해당 단계 롤백
- 단계 1 롤백: `--reason` 없는 호출에 구 동작(`.done` 자동 생성) 복원
- 단계 2 롤백: dispatch.py 자동 트리거 비활성화, 수동 분류로 전환
- 단계 3은 독립 비활성화 가능 (대시보드만 제거)

---

## 10. 한계 및 미해결 항목

### 10.1 UNCLASSIFIED 케이스 처리 절차

우선순위 1~8 평가 후 미매칭 시:
1. `UNCLASSIFIED` 이벤트 발행 (task_id, evidence snapshot, timestamp 포함)
2. 회장 알림 채널에 즉시 통보
3. 회장 발화 대기 → 회장이 분류 지정 시 해당 분류 마커 수동 발행
4. 회장 발화가 48h 내 없을 경우 자동으로 `WAITING_FOR_CHAIR_DECISION`으로 격상

### 10.2 외부 workspace dirty owner 식별 자동화 한계

현재 `workspace_dirty_owner` 판별은 아래 git 명령 기반이며 자동화 한계 존재:

```bash
git status --porcelain          # 변경 파일 목록
git log --oneline <file>        # 파일별 최근 커밋 작성자/task 추적
git blame <file> -L 1,5         # 라인별 owner 추적
```

**한계사항:**
- 시스템 활동 파일(lock 파일, 로그 등)은 커밋 author로 owner 판별 불가
- 복수 task가 동일 파일 수정 시 owner 분리 불가
- staged 파일이 타 task 소속임을 자동 확인하는 메타데이터 부재
- 현재: 사람이 파일 경로와 task 범위를 대조하여 수동 판별

**Phase B 개선 방향** (회장 재결정 영역):
- task-scope 메타데이터 파일 도입 (`task-scope.json`)을 통한 파일 owner 자동 매핑
- 운영 복사본 패턴 정의 (`systemd/`, `.service` 등) 자동 외부 판별

### 10.3 신뢰도 임계값 미정

- confidence 임계값(예: 0.4 이하 시 WAITING_FOR_CHAIR_DECISION 강제) 기준값은 Phase B 운영 데이터 수집 후 확정
- 현재 제안값: 임계값 0.4 (경계 케이스 회장 회람 기준)
- 회귀 fixtures 5개 PASS 후 임계값 조정 여부 회장 결정

### 10.4 DOGFOODING_PENDING 재분류 트리거 자동화 한계

- 현재: `.done.escalated` → `.dogfooding-pending` rename은 수동 조작
- Phase B 자동화 목표: qc_result + worktree commit + PR check 자동 검증 후 재분류 자동 발행
- BOT 토큰 갱신 등 인프라 복구 감지 자동화 선행 필요

### 10.5 task-2486 머지 전 production 반영 불가

- 본 명세의 8종 분류 enum은 task-2486 머지 완료 전까지 production 미반영이 원칙
- task-2486 내용(Phase B 기반 시스템 변경)이 본 명세와 충돌 없음을 머지 전 확인 필요
- 확인 주체: 페룬(팀장) + 회장 최종 승인

---

## 11. Codex 사전 검증 결과 (2026-05-08)

본 명세 + classifier 구현에 대한 Codex 사전 검증 결과:
- pass: true
- critical: False
- risks: 6 (high 2 / medium 3 / low 1)

### High severity 보강 사항 (반영 완료)

- **DONE 규칙 강화**: essence_verdict == PASS + pr_merged_at IS NOT NULL 추가 (classifier R1)
- **MERGE_PENDING_DEPENDENCY 규칙 강화**: pr_merged_at IS NULL + ci_fail_owner == EXTERNAL_DEPENDENCY + done_marker_exists == False 추가 (classifier R4)

### Medium severity 한계 명시 (Phase B 진입 시 결정)

- BLOCKED_BY_EXTERNAL_DEPENDENCY / FAILED_PREEXISTING의 pr_state == OPEN 검증 추가 (반영)
- UNCLASSIFIED enum 운용 방안 (섹션 9 참조)
- task-2485 fixture 신뢰도 (섹션 8 참조)

### Low severity (Phase B 운영 데이터 축적 후 보강)

- 부정 테스트 (false-positive 방지) 보강 — Phase B 진입 후 실증 데이터로 보강
