<!-- task=m-agent-task-runtime-target-selector/04+03_failover_budget plan=10 tag=REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API -->

# Plan - Failover audit·native provenance·actual budget evidence 완결

## 이 파일을 읽는 구현 에이전트에게

구현과 검증을 끝낸 뒤 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 내용과 원문 출력으로 채운다. active 파일은 그대로 두고 review 준비 완료만 보고한다. 종결은 code-review skill 전용이다. 차단되면 정확한 원인, 시도한 명령/출력, 재개 조건만 구현 에이전트 소유 evidence에 기록한다. 사용자에게 질문하거나 user-input 도구를 호출하지 않고, control-plane stop file 생성, 다음 상태 분류, archive/log 이동, `complete.log` 작성은 하지 않는다.

## 배경

열 번째 리뷰에서 focused 6개와 전체 179개 Python suite, `py_compile`, `git diff --check`는 통과했지만 별도 최소 재현 3개가 여전히 실패했다. non-quota failover는 prior audit를 덮어쓰고, same-Pi resume은 locator attempt 밖 native file을 승인하며, Codex 실제 invocation의 budget audit는 최초 Claude target과 `initial`을 기록한다. active CODE_REVIEW의 구현 소유 evidence도 전부 비어 있다. live selector invocation은 후속 `05+04_dispatch_integration` 범위로 유지한다.

## Archive Evidence Snapshot

- Task: `agent-task/m-agent-task-runtime-target-selector/04+03_failover_budget`
- Archived plan: `agent-task/m-agent-task-runtime-target-selector/04+03_failover_budget/plan_local_G08_9.log`
- Archived review: `agent-task/m-agent-task-runtime-target-selector/04+03_failover_budget/code_review_cloud_G08_9.log`
- Verdict: `FAIL`
- Findings: Required 4, Suggested 0, Nit 0
- Summary: non-quota audit 보존, same-Pi native provenance, actual invocation target/transition budget audit, 구현 evidence가 완료되지 않았다.
- Affected files: `scripts/select_execution_target.py`, `scripts/dispatch.py`, `tests/test_select_execution_target.py`, `tests/test_dispatch.py`
- Verification evidence: focused selector 3개, context/budget 3개, 전체 Python suite 179개, `py_compile`, `git diff --check`는 PASS했다. 별도 현재-checkout 재현은 `available→unknown`, locator 밖 native file 승인, actual Codex invocation에도 `last_target=claude`와 `last_transition=initial`이 남는 결함을 확인했다.
- Roadmap carryover: S06 `context-failover`, S07 `failure-budget`; 이 subtask는 기반만 닫고 Milestone Task 완료 체크는 `05+04_dispatch_integration`이 소유한다.

## 분석 결과

### 읽은 파일

- `agent-roadmap/phase/automation-runtime-bridge/PHASE.md`
- `agent-roadmap/phase/automation-runtime-bridge/milestones/agent-task-runtime-target-selector.md`
- `agent-roadmap/sdd/automation-runtime-bridge/agent-task-runtime-target-selector/SDD.md`
- `agent-ops/skills/project/orchestrate-agent-task-loop/SKILL.md`
- `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/select_execution_target.py`
- `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py`
- `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_select_execution_target.py`
- `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py`
- `agent-test/local/rules.md`
- `agent-ops/rules/common/rules-agent-spec.md`, `agent-spec/index.md`, `agent-contract/index.md`
- 위 Archive Evidence Snapshot의 plan/review log와 predecessor `agent-task/archive/2026/07/m-agent-task-runtime-target-selector/03+01,02_route_pin_state/complete.log`

### SDD 기준

- SDD는 `[승인됨]`, 잠금 `해제`, `USER_REVIEW.md` 없음이다.
- S06 `context-failover`/`context-failover` Evidence Map은 PLAN·locator·normalized/raw/workspace logical context와 same-Pi native identity, no-bounce를 요구한다. API-1/2와 selector/context focused 검증으로 역산했다.
- S07 `failure-budget`/`failure-budget` Evidence Map은 동일 work unit/stage의 target 전환·restart 공유, stage 분리, 성공 reset을 요구한다. API-3와 actual `run_escalating()` persistent lifecycle 검증으로 역산했다.
- 이 subtask PASS만으로 S06/S07의 live dispatcher integration을 완료하지 않으므로 `Roadmap Targets`는 두지 않는다.

### 테스트 환경 규칙

- `test_env=local`; `agent-test/local/rules.md`를 읽고 현재 checkout에서 검증한다.
- 경로 매핑상 별도 local smoke profile은 이 Python dispatcher 내부 계약에 매칭되지 않는다. repository Python unittest, `py_compile`, `git diff --check`를 fallback 검증 원본으로 사용한다.
- 외부 provider, credential, Edge-Node process가 필요하지 않다. 고정 snapshot, fake subprocess JSON, 임시 locator와 재open한 `StateStore`만 사용한다.

### 테스트 커버리지 공백

- `tests/test_select_execution_target.py:993-1032`는 provider-quota 성공/no-bounce만 검사해 non-quota prior/result audit tuple을 비교하지 않는다.
- `tests/test_dispatch.py:5262-5299`는 실제 `invoke()` locator가 아니라 수동 locator를 만들고 외부 native file을 same-Pi로 승인한다.
- `tests/test_dispatch.py:5327-5394`는 initial Pi spec의 generic failure만 반복해 primary→alternate actual target/transition audit를 실행하지 않는다.
- focused/full suite PASS와 위 세 최소 재현 실패가 함께 확인되어 verification evidence는 재현 가능하지만 기존 coverage는 불충분하다.

### 심볼 참조

- rename/remove와 새 package/import는 없다.
- `_failover()`는 selector CLI/`select_execution_target()`이, `build_context_package()`는 failover continuation 경계가, `StageFailureBudget.record_failure()`는 `run_escalating()`이 소비한다.

### 분할 판단

- split 정책을 재평가했다. 네 Required는 같은 S06/S07 기반 pair의 selector audit, locator provenance, runtime budget, evidence closure이며 동일 네 파일의 계약을 함께 닫는다.
- selector/context/budget을 새 sibling으로 나누면 현재 `04+03_failover_budget`의 persisted state와 review evidence를 중복 소유하므로 단일 follow-up이 더 안전하다.
- 디렉터리 의존성 `+03`은 archived predecessor `agent-task/archive/2026/07/m-agent-task-runtime-target-selector/03+01,02_route_pin_state/complete.log`의 PASS로 충족됐다.

### 범위 결정 근거

- `05+04_dispatch_integration`이 소유하는 live failover selector invocation, completing-target selfcheck, dependency drain/throughput는 제외한다.
- 새 wire/public API, dependency, agent-spec, agent-contract, roadmap 갱신은 없다. task-group `WORK_LOG.md`는 dispatcher 소유라 수정하지 않는다.

### 최종 라우팅

- `evaluation_mode=isolated-reassessment`; 이전 lane/G·점수·filename은 중립 입력에서 제외했다.
- build/review closures는 모두 true다. scope는 네 Required와 네 파일로 닫혔고, context는 selector/dispatcher/tests/SDD로 닫혔으며, 검증은 focused/full/reproducer로 결정적이다. 실제 checkout 재현으로 evidence가 신뢰 가능하고, task-local write-set/dispatcher-owned log 경계가 명확하며, Milestone 결정 필요가 없어 ownership/decision도 닫혔다.
- build `route_basis=local-fit`, `capability_gap=none`, scores=`2/2/1/2/1`, grade sum=`8`. loop-risk는 `temporal_state`, `boundary_contract`, `structured_interpretation`, `variant_product`가 일치하고 `triggered=true`; concurrent actor signature는 불일치다.
- review `route_basis=official-review`, `capability_gap=none`, scores=`2/2/1/2/1`, grade sum=`8`. G floor/추가 floor는 없음이다.
- `finalizer=finalize-task-policy.sh`, `finalizer_mode=pair`; build=`local-G08`, `PLAN-local-G08.md`; review=`cloud-G08`, `CODE_REVIEW-cloud-G08.md`; reviewer=`codex/gpt-5.6-sol xhigh`.

## 구현 체크리스트

- [ ] non-quota qualified failover가 prior candidate audit를 그대로 보존하고 provider-quota만 failed target을 exhausted로 표시하도록 수정·회귀 검증한다.
- [ ] same-Pi native session을 locator attempt의 `pi-sessions`와 non-empty session identity에 묶고 cross-adapter native 비유출을 fail-closed 검증한다.
- [ ] fake Pi JSON subprocess의 실제 `invoke()` locator에서 raw/normalized/native path와 normalized order를 확인하고 task/workspace/PLAN/artifact/native 누락·불일치·외부 경로 matrix를 검증한다.
- [ ] 실제 `run_escalating()`과 재open한 `StateStore`로 primary 1회→alternate 9회 budget, actual target/transition audit, stage 분리, 성공 reset, retry-blocked 보존을 검증한다.
- [ ] focused tests, 전체 Python suite, `py_compile`, `git diff --check`를 통과한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

### [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-1] Non-quota audit 보존

- 문제: `select_execution_target.py:601-609`는 모든 qualified failover의 cloud candidate audit를 snapshot으로 재계산한다. snapshot 없는 non-quota 실패가 prior `available`을 `unknown`으로 바꾼다.
- 해결 방법: non-quota에서는 `current = dict(candidate)`의 prior audit tuple을 유지한다. provider-quota일 때만 failed target을 `exhausted/ineligible/quota_exhausted`로 강제하고 다른 candidate는 명시적 matching snapshot이 있을 때만 갱신한다.

```python
# Before: select_execution_target.py:601-609
status = "exhausted" if provider_quota_failed_target else _snapshot_status(...)
current["quota_status"] = status

# After
if provider_quota_failed_target:
    set_exhausted(current)
elif failure_class == "provider-quota" and matching_snapshot_exists(current):
    apply_snapshot_audit(current)
# non-quota는 prior audit를 보존한다.
```

- 수정 파일 및 체크리스트: [ ] `scripts/select_execution_target.py`; [ ] `tests/test_select_execution_target.py`.
- 테스트 작성: `SelectorFailoverContractTests`에 failure class × prior `available|unknown|exhausted`의 before/after `(quota_status, eligibility, rejection_reason)` table과 no-bounce를 추가한다.
- 중간 검증: `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_select_execution_target.py SelectorFailoverContractTests -v` — PASS, exit 0.

### [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-2] Same-Pi native provenance

- 문제: `dispatch.py:2864-2868`은 same-Pi native file의 존재만 검사해 locator attempt 밖 파일, 빈/불일치 session identity를 승인한다.
- 해결 방법: locator `session_id`가 non-empty인지 확인하고 native path가 absolute이며 `locator.parent/pi-sessions` 아래의 `.jsonl`이고 basename이 해당 session id를 포함하는지 검증한다. cross-adapter package에는 native field를 넣지 않는다.

```python
# Before: dispatch.py:2864-2868
native = Path(str(record.get("native_session_path", "")))
if not native.is_file():
    raise ExecutionDecisionError(...)

# After
session_id = require_non_empty_session_id(record)
native = require_pi_native_for_attempt(locator.parent, session_id, record)
```

- 수정 파일 및 체크리스트: [ ] `scripts/dispatch.py`; [ ] `tests/test_dispatch.py`.
- 테스트 작성: actual fake Pi JSON `invoke()` artifact를 fixture로 삼아 task/workspace/PLAN/raw/normalized/native의 missing, mismatch, path escape, session mismatch와 cross-adapter native 비유출을 table/subTest로 검증한다.
- 중간 검증: `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py DynamicFailoverBudgetTest -v` — context/invoke cases PASS, exit 0.

### [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-3] Actual invocation budget audit

- 문제: `dispatch.py:3063-3066`은 현재 invocation `spec` 대신 persisted initial decision의 selected/trigger를 failure audit에 전달한다.
- 해결 방법: `run_escalating()`의 현재 `spec`을 canonical target ref로 변환해 매 실패에 기록한다. 시작 spec이 persisted selected와 다르거나 recovery 중 spec이 바뀌면 actual transition을 `failover`로 유지하고, 동일 target initial/resume은 persisted transition을 사용한다. budget key `work_unit_id|stage`는 바꾸지 않는다.

```python
# Before: dispatch.py:3063-3066
selected = state["execution_decisions"][role]["selected"]
transition = state["execution_decisions"][role]["transition"]["trigger"]
stage_budget.record_failure(target=selected, transition=transition)

# After
stage_budget.record_failure(
    target=canonical_target_ref(spec),
    transition=actual_invocation_transition,
)
```

- 수정 파일 및 체크리스트: [ ] `scripts/dispatch.py`; [ ] `tests/test_dispatch.py`.
- 테스트 작성: actual primary failure 1회 뒤 store를 reopen하고 alternate spec으로 9회 실패시켜 정확히 10번째 block, alternate `last_target`, `last_transition=failover`, review count 0, worker success reset, `clear_blocked()` route/history 보존을 검증한다.
- 중간 검증: `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py DynamicFailoverBudgetTest -v` — lifecycle cases PASS, exit 0.

### [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-4] 구현 evidence 완결성

- 문제: archived review의 완료 표, 체크리스트, 계획 편차, 설계 결정, 검증 출력이 비어 있어 구현 주장을 검증할 수 없다.
- 해결 방법: 실제 변경과 아래 명령의 stdout/stderr/exit code를 active CODE_REVIEW 구현 소유 섹션에 기록한다. 명령 변경은 `계획 대비 변경 사항`에 이유와 대체 명령을 남긴다.
- 수정 파일 및 체크리스트: [ ] `agent-task/m-agent-task-runtime-target-selector/04+03_failover_budget/CODE_REVIEW-cloud-G08.md`.
- 테스트 작성: 코드 테스트가 아니라 active review의 구현 소유 `[ ]`, placeholder, 빈 검증 결과가 없는지 확인한다.
- 중간 검증: `rg -n '^_.*_$|\| \[ \] \||^- \[ \]' agent-task/m-agent-task-runtime-target-selector/04+03_failover_budget/CODE_REVIEW-cloud-G08.md` 결과를 검토해 review-agent 전용 체크박스 외 구현 소유 미완료가 없어야 한다.

## 의존 관계 및 구현 순서

1. API-1 audit contract를 닫는다.
2. API-2 actual invoke artifact와 native validator를 연결한다.
3. API-3 persistent budget actual target/transition을 검증한다.
4. API-4로 active CODE_REVIEW evidence를 실제 결과와 동기화한다.

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/select_execution_target.py` | API-1 |
| `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_select_execution_target.py` | API-1 |
| `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py` | API-2, API-3 |
| `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py` | API-2, API-3 |
| `agent-task/m-agent-task-runtime-target-selector/04+03_failover_budget/CODE_REVIEW-cloud-G08.md` | API-4 |

## 최종 검증

- `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_select_execution_target.py SelectorFailoverContractTests -v` — audit table/no-bounce PASS, exit 0.
- `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py DynamicFailoverBudgetTest -v` — actual invoke provenance와 primary→alternate restart budget PASS, exit 0.
- `python3 -m unittest discover -s agent-ops/skills/project/orchestrate-agent-task-loop/tests -p 'test_*.py'` — fresh-process 전체 suite PASS.
- `python3 -m py_compile agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py agent-ops/skills/project/orchestrate-agent-task-loop/scripts/execution_target_policy.py agent-ops/skills/project/orchestrate-agent-task-loop/scripts/select_execution_target.py agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_execution_target_policy.py agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_select_execution_target.py` — exit 0.
- `git diff --check` — 출력 없음, exit 0.
