# task-2560 보고서 — FUC-4 per-PR exception isolation (ExecutorScheduler hardening)

## 결과 한 줄

`ExecutorScheduler._handle_single_diagnosis` per-PR try/except isolation 박제. 한 PR 예외가 cycle 전체를 중단하지 않게 보장. 실패 PR 만 FAILED 또는 critical-escalated marker 박제. 회장 §명시 2026-05-12 Track A 1순위 본질 1:1 + 필수 구현 7 + 필수 테스트 6 충족.

## 회장 §명시 본질 1:1 박제

| § | 본질 | 박제 위치 |
|---|---|---|
| 1 | `_handle_single_diagnosis` per-PR try/except isolation | `executor_scheduler.py::_safe_handle_single_diagnosis` |
| 2 | 실패 PR 만 FAILED/ESCALATED marker | `_record_pr_exception_marker` — `.failed` / `.critical-escalated` |
| 3 | 다음 PR diagnosis 진행 | `run_one_cycle` for-loop wrapper 호출 후 continue 보장 |
| 4 | cycle 전체 중단 0 | `SchedulerCycleResult.cycle_crashed=False` 어셀션 |
| 5 | exception summary decision/audit 기록 | `.exception.json` (schema v1) + scheduler audit JSONL |
| 6 | Critical 7 이면 critical escalation marker | `_CRITICAL_EXCEPTIONS` 7개 + `.critical-escalated` |
| 7 | non-critical exception 이면 cycle 유지 | per-PR action 만 ISOLATED, run_one_cycle 정상 return |

## 필수 테스트 6 박제

`anu_v2/tests/test_executor_scheduler_per_pr_isolation.py` — 7/7 PASS:

1. **PR A exception raise** — `test_pr_a_exception_isolated_pr_b_proceeds` (RuntimeError 시뮬레이션)
2. **PR B 정상 처리** — 같은 테스트, action_b.action == OWNER_TRIGGER_DISPATCHED + HTTP POST 1회
3. **failed PR marker 생성** — `task-9001.pr-9001.failed` + `task-9001.pr-9001.exception.json` 박제
4. **next PR owner_trigger path 유지** — PR B `/repos/test-owner/test-repo/issues/9002/comments` 실제 호출 검증
5. **whole cycle crash 0** — `result.cycle_crashed is False` + isinstance(result, SchedulerCycleResult)
6. **long polling 0** — `test_long_polling_zero_cycle_returns_immediately` (elapsed < 5s, rechecks_done == 0)

부수 박제 3:
- `test_critical_exception_escalated_marker` — PermissionError → ACTION_PR_EXCEPTION_CRITICAL_ESCALATED
- `test_bot_session_exit_required_not_swallowed` — BotSessionExitRequired 는 re-raise (§9)
- `test_critical_exception_classification_matches_7` — `_CRITICAL_EXCEPTIONS` 정확히 7개

## Critical 7 분류 (회장 §명시 본질 6)

`_CRITICAL_EXCEPTIONS` (anu_v2/executor_scheduler.py):

1. `TokenBoundaryViolation` — scheduler token 경계 위반 (§11)
2. `PermissionError` — OS 권한
3. `OSError` — 디스크/파일시스템/네트워크 OS 레벨
4. `MemoryError` — 자원 고갈
5. `NotImplementedError` — 필수 인터페이스 미구현
6. `TypeError` — 타입 계약 위반 (DI 잘못)
7. `AttributeError` — 내부 attribute 누락 (코드 인터페이스 갈림)

이 7개 중 하나라도 발생 시 `.critical-escalated` marker 박제 + 같은 cycle 의 다음 PR 은 정상 진행.

## 회장 §명시 expected_files 8건 strict

1. `anu_v2/executor_scheduler.py` — per-PR isolation wrapper + `_record_pr_exception_marker` + 신규 상수 (수정)
2. `anu_v2/tests/test_executor_scheduler_per_pr_isolation.py` — 7 회귀 테스트 (신규)
3. `anu_v2/fixtures/executor_per_pr_exception_isolation.json` — PR A/B/C 3 snapshot fixture (신규)
4. `memory/reports/task-2560.md` — 본 보고서 (신규)
5. `memory/events/task-2560.dispatch-decision.json` — authoritative dispatch decision (신규)
6. `memory/plans/tasks/task-2560/plan.md`
7. `memory/plans/tasks/task-2560/context-notes.md`
8. `memory/plans/tasks/task-2560/checklist.md`

## forbidden 0 어셀션 (회장 §명시 7 금지)

PR 변경 파일 8건 전부 expected_files 내부, forbidden 0:

- ❌ `anu_v2/owner_trigger_only.py` 변경 0
- ❌ `anu_v2/auto_gemini_triage.py` 변경 0
- ❌ task-2558 expected_files (PR #111 영역) 재수정 0
- ❌ task-2562 (G4 Task B) 와 같은 PR 섞기 0
- ❌ task-2561 (baseline noise) 와 같은 PR 섞기 0
- ❌ 회장 수동 `/gemini review` 0 (capability 자동 호출 예정)
- ❌ force push / rebase / admin override 0
- ❌ PR #98~#111 branch 변경 0
- ❌ 다른 anu_v2 modules (idle_pr_diagnoser / merge_queue_executor / polling_policy / 기타) 변경 0
- ❌ dashboard / scripts/ci.sh / .github/workflows / .env(.keys) 변경 0

## 회귀 어셀션

`python3 -m pytest anu_v2/tests/` — **455/455 PASS** (신규 7 + 기존 448 회귀 0).

## 변경 파일 diff 요약

`anu_v2/executor_scheduler.py`:
- 신규 상수 7개 (ACTION_PR_EXCEPTION_ISOLATED, ACTION_PR_EXCEPTION_CRITICAL_ESCALATED, PR_EXCEPTION_AUDIT_SCHEMA, `_CRITICAL_EXCEPTIONS`, `_is_critical_exception`, `_summarize_exception`, TokenBoundaryViolation import)
- `SchedulerPRAction.__post_init__` 허용 action set 2개 확장
- `SchedulerCycleResult` 3 필드 확장 (`pr_exceptions_isolated`, `pr_exceptions_critical_escalated`, `cycle_crashed`)
- `run_one_cycle` for-loop 1줄 교체 (`_handle_single_diagnosis` → `_safe_handle_single_diagnosis`) + 카운터 누적
- 신규 메서드 2개: `_safe_handle_single_diagnosis`, `_record_pr_exception_marker`
- `__all__` 3 항목 export 추가

## 14단계 standard lifecycle

| 단계 | 상태 | 비고 |
|---|---|---|
| cokacdir → workspace | ✅ | `/home/jay/.cokacdir/workspace/57CF6414` → `/home/jay/workspace` |
| worktree task-2560-dev5 | ✅ | from origin/main @ `9a09ab3a` |
| 코드 수정 + 회귀 PASS | ✅ | 455/455 |
| 3문서 + dispatch + 보고서 | ✅ | 본 PR 포함 |
| BOT identity commit | 진행 예정 | `BOT_USER_NAME` / `BOT_USER_EMAIL` |
| PR 생성 (BOT) | 진행 예정 | head=task-2560-dev5, base=main |
| Gemini fresh | 진행 예정 | owner_trigger_only / executor_scheduler 자동 (capability 8번째) |
| unresolved 0 | 진행 예정 | thread 모두 RESOLVED |
| CI 11/11 | 진행 예정 | required checks |
| CLEAN | 진행 예정 | mergeStateStatus=CLEAN |
| BOT squash merge | 진행 예정 | admin override 0 |
| post-merge smoke + reconcile | 진행 예정 | task-2560 lifecycle marker |
| .done | 진행 예정 | 회장 보고 직후 |

## 최종 원칙 자기검증

executor_scheduler hardening. cycle 안정성 보강. task-2558 영역 mutate 0. one-way isolation (anu_v2/* 만 import) 유지. chat_notifications == 0 (회장 §8 1:1). long polling 0 (회장 §9 1:1). capability 자동 호출 가능 (회장 수동 입력 0).
