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

# Plan - Failover provenance와 shared budget lifecycle 구현 재개

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

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

## 배경

여섯 번째 리뷰에서 source/test가 직전 FAIL 뒤 수정되지 않았고 active CODE_REVIEW evidence도 전부 비어 있음을 확인했다. focused selector 3개, context/budget 3개, 전체 179개 Python suite, `py_compile`, `git diff --check`는 통과하지만 non-quota audit, same-Pi native provenance, actual failover budget audit 결함을 포착하지 못한다. 최소 재현은 `available→unknown`, 외부 native file 승인, Claude→Codex 두 번째 실패의 `last_target=claude`를 확인했다. live dynamic 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_5.log`
- Archived review: `agent-task/m-agent-task-runtime-target-selector/04+03_failover_budget/code_review_cloud_G08_5.log`
- Verdict: `FAIL`
- Findings: Required 4, Suggested 0, Nit 0
- Summary: non-quota failover의 quota audit 보존, same-Pi native session provenance, 실제 primary→alternate restart 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: 리뷰어가 selector focused 3 tests, context/budget focused 3 tests, 전체 Python suite 179 tests, `py_compile`, `git diff --check`를 실행해 모두 PASS를 확인했다. 별도 재현은 non-quota prior status가 `unknown`으로 바뀌고 외부 same-Pi native file이 승인되며 runtime budget audit가 최초 target에 고정됨을 확인했다.
- Roadmap carryover: S06 `context-failover`, S07 `failure-budget`; 이 subtask는 기반을 닫고 Milestone Task 완료 체크는 `05+04_dispatch_integration`이 소유한다.

## 분석 결과

### 읽은 파일

- `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-test/local/testing-smoke.md`
- `agent-ops/rules/common/rules-agent-spec.md`
- `agent-spec/index.md`
- `agent-contract/index.md`

### SDD 기준

- SDD 상태는 `[승인됨]`, 잠금은 해제되어 있고 사용자 결정 대기는 없다.
- S06은 qualified provider target 불가, ordered unused candidate, no-bounce와 PLAN/locator/normalized/raw/workspace logical context evidence를 요구한다.
- S07은 동일 work unit/stage의 target 전환·restart 공유, 다른 stage 분리, 성공 reset과 10번째 실패 blocker evidence를 요구한다.
- 이 follow-up 단독으로 live S09 integration을 완료하지 않으므로 `Roadmap Targets`는 두지 않는다.

### 테스트 환경 규칙

- `test_env=local`; `agent-test/local/rules.md`를 적용한다.
- Edge-Node smoke 명령은 Python dispatcher 계약과 매칭되지 않아 의도적으로 사용하지 않는다.
- 외부 provider/quota 호출 없이 고정 snapshot, fake subprocess JSON stream, 임시 locator와 persistent `StateStore`를 사용한다.
- 매칭되는 `agent-spec` 또는 `agent-contract` 문서는 없어 승인 SDD, project skill과 실제 코드/테스트를 계약 근거로 사용한다.

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

- `provider-stream-disconnect` failover에 quota snapshot이 없으면 prior의 cloud `available` 상태가 `unknown`으로 바뀐다.
- same-Pi locator의 `native_session_path`를 locator attempt 밖 파일로 바꿔도 context package가 성공한다.
- `run_escalating()`은 승격 뒤 실제 `spec` 대신 persisted decision의 최초 target/transition을 `StageFailureBudget`에 기록한다.
- 현재 runtime budget test는 성공 reset 뒤 재open하여 동일 Pi generic failure 10회만 실행하므로 primary 1회→restart→alternate 9회 경계를 실행하지 않는다.
- active CODE_REVIEW는 모든 구현 소유 필드가 비어 있고 source/test는 직전 FAIL 이후 수정되지 않았다.

### 심볼 참조

- rename/remove와 새 package/import는 없다.
- `_failover()` 결과는 selector CLI와 후속 dispatcher integration이 소비한다.
- `build_context_package()`는 target transition caller가 소비하고 `invoke()`가 만든 `locator.json`, `stream.log`, `normalized-output.log`, Pi session artifact와 결합된다.
- `StageFailureBudget.record_failure()`는 `run_escalating()`의 worker/review failure와 성공 reset 경로가 소비한다.
- `StateStore.clear_blocked()`는 blocker/counter만 초기화하고 execution decision/history를 보존해야 한다.

### 분할 판단

- split 정책을 먼저 평가했다. 네 Required는 S06/S07 기반 pair의 provenance, runtime lifecycle, evidence closure이며 동일 네 파일을 함께 사용한다.
- 추가 분할은 selector/context/budget 사이의 half-contract와 write-set 충돌을 만들므로 현재 subtask의 단일 follow-up으로 유지한다.
- live selector failover invocation, completing target selfcheck, scheduler/throughput rollout은 기존 `05+04_dispatch_integration` 범위로 유지한다.

### 범위 결정 근거

- 현재 네 source/test와 active review evidence만 수정한다. live selector invocation, completing-target selfcheck, dependency drain/throughput는 후속 `05+04_dispatch_integration` 소유이므로 제외한다.
- 새 package, public wire/API, agent-spec/agent-contract/roadmap 변경은 필요하지 않다.

### 최종 라우팅

- evaluation_mode: `isolated-reassessment`.
- finalizer=`finalize-task-policy.sh`, finalizer_mode=`pair`; build/review status=`routed`.
- build/review closures의 `scope_closed`, `context_closed`, `verification_closed`, `evidence_trusted`, `ownership_closed`, `decision_closed`는 모두 `true`다. concrete repro, 승인 SDD, 네 source/test write-set과 deterministic unittest로 성공 조건을 확정했다.
- build route_basis=`local-fit`, capability_gap=`none`.
- build loop-risk matched signatures=`temporal_state,boundary_contract,structured_interpretation,variant_product`, triggered=`true`; failover/restart/retry/success/block 순서, selector/context/store 경계, JSON locator와 failure/quota/stage variant가 근거다.
- build scores(scope_coupling/state_concurrency/blast_irreversibility/evidence_diagnosis/verification_complexity)=`2/2/1/2/1`, final=`local-G08`, filename=`PLAN-local-G08.md`.
- review route_basis=`official-review`, capability_gap=`none`.
- review scores=`2/2/1/2/1`, final=`cloud-G08`, filename=`CODE_REVIEW-cloud-G08.md`; execution=`codex/gpt-5.6-sol xhigh`.

## 구현 체크리스트

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

## 구현 항목

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

- 문제: `select_execution_target.py:601-609`의 `_failover()`가 provider-quota의 실패 target을 제외한 모든 cloud candidate를 선택적 snapshot으로 다시 계산한다. snapshot이 없으면 prior `available`이 `unknown`으로 바뀐다.
- 해결 방법:

```python
if failure_class == "provider-quota" and target_ref == previous:
    apply_quota_status(current, "exhausted")
elif failure_class == "provider-quota":
    apply_snapshot_if_present(current, quota_snapshot)
else:
    preserve_prior_quota_fields(current)
```

  non-quota qualified failure는 prior `quota_status`, `eligibility`, `rejection_reason`을 바꾸지 않는다. provider-quota는 실패 target만 강제 exhausted로 만들고 다른 target은 명시적 snapshot이 있을 때만 갱신한다. used candidate와 no-bounce는 유지한다.
- 수정 파일 및 체크리스트:
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/select_execution_target.py`: failure-class별 quota audit 갱신 경계.
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_select_execution_target.py`: provider-quota/non-quota × available/unknown/exhausted table과 resume/no-bounce 회귀.
- 테스트 작성: prior candidate 상태와 result candidate 상태를 모두 비교해 failed/alternate candidate audit가 의도한 경우에만 바뀌는지 검증한다.
- 중간 검증: `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_select_execution_target.py SelectorFailoverContractTests -v` — 모든 case PASS, exit 0.

### [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-2] Logical context native provenance

- 문제: `dispatch.py:2858-2868`은 raw/normalized를 locator sibling으로 제한됐지만 same-Pi native session은 파일 존재만 확인해 다른 attempt/session의 파일을 승인한다.
- 해결 방법:

```python
require_exact_attempt_artifact(locator.parent, "stream.log", record["stream_log"])
require_exact_attempt_artifact(locator.parent, "normalized-output.log", record["normalized_output_log"])
require_pi_session_identity(locator.parent / "pi-sessions", record["session_id"], record["native_session_path"])
```

  same-Pi에서는 non-empty session id, absolute native path, `locator.parent/pi-sessions` 소속과 filename의 session identity를 검증한다. cross-adapter package에는 native path를 포함하지 않는다. task/workspace/PLAN/raw/normalized의 기존 exact identity도 유지한다.
- 수정 파일 및 체크리스트:
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py`: same-attempt/session native validator.
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py`: actual invoke artifact와 context provenance matrix.
- 테스트 작성: fake Pi JSON subprocess를 실제 `invoke()`로 실행해 locator raw/normalized/native 경로와 normalized display order를 확인한다. task/workspace/PLAN/raw/normalized/native의 missing, mismatch, sibling escape, session mismatch와 cross-adapter native 비유출을 table로 검증한다.
- 중간 검증: `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py DynamicFailoverBudgetTest -v` — context/invoke case PASS, exit 0.

### [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-3] Actual target transition budget audit

- 문제: `dispatch.py:3063-3066`의 `run_escalating()`은 persisted decision에서 stage budget identity를 만들지만 각 실패의 `last_target`과 `last_transition`도 같은 최초 decision에서 읽는다. 승격 후 실제 target과 audit가 어긋난다.
- 해결 방법:

```python
budget.record_failure(
    target=target_ref_for_spec(spec),
    transition=current_runtime_transition,
)
```

  budget key는 동일 `work_unit_id|stage`를 유지하고 실패 audit만 현재 invocation의 `spec`과 transition으로 기록한다. primary qualified failure 후 alternate target으로 전환된 상태를 restart 뒤에도 식별할 최소 transition evidence를 보존한다. live dynamic selector invocation 자체는 후속 `05+04_dispatch_integration`에 남긴다.
- 수정 파일 및 체크리스트:
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py`: actual target/transition budget 기록과 restart 복원.
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py`: primary→alternate runtime lifecycle.
- 테스트 작성: 첫 `run_escalating()`에서 primary qualified failure 1회를 기록하고 deterministic terminal infrastructure result로 loop를 멈춘다. `StateStore`를 재open해 alternate spec의 추가 9회 실패로 정확히 10번째에서 block되는지, `last_target`이 alternate인지, review stage는 0인지 검증한다. 별도 성공 경로는 worker budget만 reset하고 `clear_blocked()`는 decision/history/used candidates를 보존해야 한다.
- 중간 검증: `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py DynamicFailoverBudgetTest -v` — budget lifecycle case PASS, exit 0.

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

- 문제: `CODE_REVIEW-cloud-G08.md:46-130`의 완료 표, 체크리스트, 설계 결정과 검증 출력이 비어 있어 구현 주장을 신뢰할 수 없었다.
- 해결 방법: 위 세 항목의 실제 변경과 명령 출력을 active CODE_REVIEW의 구현 소유 섹션에 즉시 동기화한다. command를 바꾸면 대체 명령과 이유를 `계획 대비 변경 사항`에 기록한다. 출력이 길면 repository 밖 저장 경로와 저장에 사용한 exact command를 기록한다.
- 수정 파일 및 체크리스트:
  - [ ] `agent-task/m-agent-task-runtime-target-selector/04+03_failover_budget/CODE_REVIEW-cloud-G08.md`: 모든 completion row/checklist, 계획 편차, 설계 결정, 실제 stdout/stderr/exit code.
- 테스트 작성: 별도 코드 테스트가 아니라 아래 고정 명령의 실제 출력과 review artifact completeness로 검증한다.
- 중간 검증: active CODE_REVIEW에서 구현 소유 `[ ]`, `_미작성_`, `_미실행_`, 빈 검증 결과가 남지 않았는지 확인한다.

## 의존 관계 및 구현 순서

1. archived predecessor `03+01,02_route_pin_state/complete.log` 충족 상태를 유지한다.
2. REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-1의 quota audit contract를 먼저 닫는다.
3. REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-2에서 actual invoke artifact와 validator를 연결한다.
4. REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-3에서 persistent budget과 actual target transition을 검증한다.
5. REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-4로 CODE_REVIEW evidence를 실제 결과와 동기화한다.
6. 이 task PASS 뒤 `05+04_dispatch_integration`이 live initial/resume/failover invocation을 연결한다.

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/select_execution_target.py` | REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-1 |
| `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_select_execution_target.py` | REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-1 |
| `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py` | REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-2, REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-3 |
| `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py` | REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-2, REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-3 |

## 최종 검증

- `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_select_execution_target.py SelectorFailoverContractTests -v` — failure/status 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; unittest cache 출력은 허용하지 않는다.
- `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.

Edge-Node user execution smoke/full-cycle은 Python dispatcher runtime contract와 매칭되는 direct user flow가 없어 실행하지 않는다. 모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다.
