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

# Logical failover context, route-family fallback와 lifecycle evidence 보완

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

구현과 테스트를 완료한 뒤 모든 검증 명령을 실행하고 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션에 실제 변경 내용과 stdout/stderr, exit code를 채운다. active PLAN/CODE_REVIEW 파일은 그대로 두고 리뷰 준비 완료만 보고한다. 최종 판정, 로그 아카이브, `complete.log`, 다음 상태 분류는 code-review skill 소유다. 차단되면 구현 소유 evidence 필드에 정확한 blocker, 시도한 명령과 출력, 재개 조건만 기록하며 사용자에게 질문하거나 user-input 도구·control-plane stop 파일을 만들지 않는다.

## 배경

KST Gemini/Laguna 후보와 기본 failover는 추가됐지만, adapter 전환 뒤 다음 invocation prompt가 logical context artifact를 전달하지 않는다. selector failover가 모든 persisted route에 적용되어 단일 후보 cloud worker의 기존 promotion을 차단하고, prior decision은 canonical 후보가 아닌 임의 candidate를 허용한다. S07 failure budget과 S10 completing-target selfcheck 테스트도 production lifecycle 대신 합성 state를 검증하므로 실제 경로 evidence가 필요하다.

## Archive Evidence Snapshot

- 직전 계획: `agent-task/m-agent-task-runtime-target-selector/04+03_failover_budget/plan_local_G08_14.log`.
- 직전 리뷰: `agent-task/m-agent-task-runtime-target-selector/04+03_failover_budget/code_review_cloud_G08_14.log` — FAIL, Required 5 / Suggested 0 / Nit 0.
- 영향 파일: `dispatch.py`, `select_execution_target.py`, `test_dispatch.py`, `test_select_execution_target.py`.
- 검증 evidence: 계획 명령의 policy 6, selector 7, dispatcher 27, 전체 189 tests와 `py_compile`/`git diff --check`는 PASS했지만, Gemini→Laguna 두 번째 prompt의 locator/raw/normalized 누락과 cloud G07 첫 provider-quota 실패의 `no_failover_candidate` 차단을 집중 재현했다.
- 로드맵 carryover: S06/S07/S10과 `context-failover`, `failure-budget`, `selfcheck-policy`가 미완료다. S02 `time-route`의 canonical 후보 자체는 유지하되 prior 검증 회귀 대상으로 포함한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/automation-runtime-bridge/milestones/agent-task-runtime-target-selector.md`
- Milestone link: [Milestone 문서](agent-roadmap/phase/automation-runtime-bridge/milestones/agent-task-runtime-target-selector.md)
- Task ids:
  - `time-route`: local-G07~G08 KST 주야간 최초 target
  - `context-failover`: Gemini↔Laguna 단방향 logical context failover
  - `failure-budget`: target 전환 전후 동일 stage 10회 실패 예산
  - `selfcheck-policy`: 실제 worker 완료 target 기반 selfcheck
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `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/scripts/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`
- `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py`
- `agent-test/local/rules.md`
- `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-contract/index.md`, `agent-spec/index.md` — 이 runtime 범위에 매칭되는 별도 계약/spec 문서 없음.

### SDD 기준

- SDD는 `agent-roadmap/sdd/automation-runtime-bridge/agent-task-runtime-target-selector/SDD.md`, 상태 `[승인됨]`, 잠금 해제다.
- S06 → `context-failover`: qualified failure의 주간 Gemini→Laguna/야간 Laguna→Gemini 전환, logical context, no bounce를 항목 1·2와 prompt/transition evidence에 반영한다.
- S07 → `failure-budget`: primary와 alternate가 worker stage 10회 예산을 공유하고 성공 때만 reset되는 것을 항목 3의 실제 `run_escalating()` 호출로 검증한다.
- S10 → `selfcheck-policy`: 실제 completing target이 Laguna인 경우만 같은 pinned worker decision으로 selfcheck하고 Gemini/cloud 완료는 생략하는 것을 항목 3에 반영한다.
- Evidence Map의 transition, stage counter, completing-target stage evidence가 최종 집중 테스트의 assertion을 결정한다.

### 테스트 환경 규칙

- `test_env=local`; `agent-test/local/rules.md`를 읽었다. 이 Python runtime 범위에 매칭되는 별도 profile route는 없다.
- repository `unittest` layout, `py_compile`, `git diff --check`, 승인 SDD를 fallback verification source로 사용한다.
- 모든 검증은 현재 checkout 안에서 실행되며 외부 provider/runner를 호출하지 않는다. 테스트 rule 유지보수는 범위가 아니므로 create-test/update-test는 필요하지 않다.

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

- `test_daytime_failover_switches_gemini_to_laguna_with_logical_context`는 target 전환만 보고 두 번째 `invoke()` prompt의 artifact path를 확인하지 않는다.
- cloud G07 단일 후보의 Claude→Codex legacy promotion을 selector failover 변경과 함께 검증하는 회귀가 없다.
- `SelectorFailoverContractTests`는 cloud G07 prior에 임의 Codex candidate를 삽입해 canonical local-G08 후보를 검증하지 않는다.
- budget 테스트는 helper로 임의 target을 기록하거나 같은 Pi target만 반복해 실제 primary→alternate history와 reopen을 검증하지 않는다.
- selfcheck 테스트는 production이 기록하지 않는 `worker_selfcheck_required`를 직접 주입하며 `run_worker()`/`run_selfcheck()`와 pin history를 호출하지 않는다.

### 심볼 참조

- rename/remove 없음.
- 변경 call sites는 `continuation_prompt()`의 `run_escalating()`, `_validate_prior_decision()`의 resume/failover selector 입력, `task_requires_selfcheck()`의 `task_stage()`, `run_worker()`와 `run_selfcheck()`다.

### 분할 판단

- split decision policy를 파일 선택 전에 평가했다. 이 dependent subtask의 predecessor `03`은 `agent-task/archive/2026/07/m-agent-task-runtime-target-selector/03+01,02_route_pin_state/complete.log`로 충족됐다.
- context prompt, route-family fallback, canonical prior validation, stage budget과 selfcheck는 같은 persisted execution decision schema와 `run_escalating()` transition lifecycle을 공유한다. API와 call-site를 분리하면 어느 child도 독립적으로 S06/S07/S10을 검증할 수 없고 동일 fixture를 중복 수정하므로 단일 plan이 더 안전하다.
- 별도 ownership/deployment 경계가 없고 각 production 수정은 같은 focused dispatcher/selector suite로 함께 회귀하므로 추가 split gate는 적용되지 않는다.

### 범위 결정 근거

- KST initial candidate 순서와 quota tri-state 정책은 이미 통과한 S02 behavior이므로 변경하지 않고 validation fixture로만 사용한다.
- G01~G06/G09~G10 route matrix, 공식 review target, 외부 quota probe, process liveness와 work-log archive는 변경하지 않는다.
- `agent-ops/rules/common/**`, `agent-ops/skills/common/**`, roadmap/SDD/contract/spec 문서는 구현 범위에서 제외한다.

### 최종 라우팅

- `evaluation_mode=isolated-reassessment`, `finalizer=finalize-task-policy.sh`, `finalizer_mode=pair`.
- Build closures: scope/context/verification/evidence/ownership/decision 모두 `true`. Build scores: scope_coupling=2, state_concurrency=2, blast_irreversibility=1, evidence_diagnosis=2, verification_complexity=1. `route_basis=local-fit`, capability gap=none, lane=`local`, grade=`G08`, filename=`PLAN-local-G08.md`.
- Build loop-risk: temporal_state=true, concurrent_consistency=true, boundary_contract=true, structured_interpretation=false, variant_product=true, `triggered=true`; unknown 없음.
- Review closures: scope/context/verification/evidence/ownership/decision 모두 `true`. Review scores: scope_coupling=2, state_concurrency=2, blast_irreversibility=1, evidence_diagnosis=2, verification_complexity=1. `route_basis=official-review`, lane=`cloud`, grade=`G08`, filename=`CODE_REVIEW-cloud-G08.md`, target=`codex/gpt-5.6-sol xhigh`.
- grade floor와 capability-gap 승격은 none이다. 반복 횟수와 이전 route는 평가 입력이나 점수에 사용하지 않았다.

## 구현 체크리스트

- [ ] REVIEW_REVIEW_REVIEW_REVIEW_API-1 cross-adapter continuation prompt에 검증된 logical context를 전달하고 selector failover를 canonical multi-target route에만 적용해 cloud/review legacy recovery를 보존한다.
- [ ] REVIEW_REVIEW_REVIEW_REVIEW_API-2 prior decision의 candidates/selected/used candidates를 현재 canonical policy와 대조하고 실제 local-G08 주야간 failover 계약 테스트로 교체한다.
- [ ] REVIEW_REVIEW_REVIEW_REVIEW_API-3 실제 primary→alternate stage budget/reopen과 completing-target worker→selfcheck pin lifecycle을 production entry point로 검증한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

### [REVIEW_REVIEW_REVIEW_REVIEW_API-1] Logical context prompt와 route-family fallback 경계

#### 문제

`dispatch.py:2900-2915`는 `local_pi=True`인 다음 target에 locator를 넣지 않는 PLAN-only prompt를 반환하고, `dispatch.py:3122-3154`는 모든 persisted route에 selector failover/no-candidate blocker를 적용한다. 그 결과 Gemini→Laguna의 logical artifact가 다음 invocation에 전달되지 않고 cloud G07 Claude는 기존 Codex promotion 전에 차단된다.

```python
# Before: dispatch.py:2900-2915, 3122-3131
if local_pi:
    target = task.plan or task.directory
    return f"Think in English. Final in Korean. Read {target.resolve()} and complete the task."
if isinstance(store, StateStore) and failure in QUALIFIED_FAILOVER_FAILURES:
    next_decision, next_spec = persisted_execution_decision(..., transition="failover")
    context = build_context_package(...)
```

#### 해결 방법

canonical decision이 실제 multi-target Gemini/Laguna route인지 판별하는 작은 predicate를 두고 그 경우에만 selector failover와 `no_failover_candidate` terminal을 적용한다. 다른 cloud/review route는 현재 same-target retry와 `promoted_spec()` chain으로 내려간다. adapter가 바뀌면 `build_context_package()`의 PLAN/locator/workspace/raw/normalized 경로를 deterministic prompt에 직렬화하고, same-Pi native resume behavior는 유지한다.

```python
# After
if canonical_selector_failover_route(decision) and failure in QUALIFIED_FAILOVER_FAILURES:
    next_decision, next_spec = persisted_execution_decision(..., transition="failover")
    context = build_context_package(...)
    continuation = logical_context_prompt(context)
else:
    next_spec = promoted_spec(spec, codex_recovery_count)
```

#### 수정 파일 및 체크리스트

- [ ] `scripts/dispatch.py`: route-family guard와 logical-context continuation prompt를 구현하고 dead `context` assignment를 제거한다.
- [ ] `tests/test_dispatch.py`: Gemini→Laguna 두 번째 invocation prompt와 cloud G07 Claude→Codex recovery를 assertion한다.

#### 테스트 작성

- `DispatcherCanonicalFailoverIntegrationTest.test_daytime_failover_switches_gemini_to_laguna_with_logical_context`: 두 번째 prompt에 plan, locator, workspace, stream, normalized artifact의 canonical absolute path가 모두 있는지 검증한다.
- cloud G07 provider-quota 회귀: 첫 실패 뒤 Codex Terra promotion이 실행되고 `no_failover_candidate`로 차단되지 않는지 검증한다.

#### 중간 검증

```bash
export IOP_FORCE_GEMINI_TODAY=2026-07-26
python3 -m unittest agent-ops.skills.project.orchestrate-agent-task-loop.tests.test_dispatch.DispatcherCanonicalFailoverIntegrationTest agent-ops.skills.project.orchestrate-agent-task-loop.tests.test_dispatch.ReviewRetryTest -v
```

예상 결과: logical prompt artifact와 canonical/legacy recovery 회귀가 모두 PASS한다.

### [REVIEW_REVIEW_REVIEW_REVIEW_API-2] Canonical prior decision validation

#### 문제

`select_execution_target.py:279-285`는 prior decision의 shape만 검증한다. `test_select_execution_target.py:1005-1014`는 cloud G07 policy 결과에 Codex candidate를 수동 삽입하고 selector가 이를 정규 alternate로 반환하도록 허용한다.

```python
# Before: select_execution_target.py:279-285
def _validate_prior_decision(value: object) -> dict:
    """Only container/field/type/enum shape is enforced here."""
```

#### 해결 방법

resume/failover 입력의 work unit, stage, lane, grade와 동일한 evaluated policy에서 canonical candidate identities/order를 다시 산출한다. prior `candidates`, `selected`, `used_candidates`가 그 집합·순서·prefix 규칙을 벗어나면 `malformed_prior_decision`으로 fail closed한다. manual override처럼 initial policy가 의도적으로 생성한 reason/schema는 canonical 재산출 입력에 동일하게 반영한다.

```python
# After
canonical = policy.select_policy(...)
validate_prior_candidate_identity(prior, canonical.candidates)
```

#### 수정 파일 및 체크리스트

- [ ] `scripts/select_execution_target.py`: canonical candidate identity/order와 selected/used prefix validation을 추가한다.
- [ ] `tests/test_select_execution_target.py`: 합성 cloud candidate를 제거하고 local-G08 주간 Gemini→Laguna, 야간 Laguna→Gemini, tampered prior 거부를 검증한다.

#### 테스트 작성

- `SelectorFailoverContractTests`: 실제 policy prior로 qualified failover, quota exhausted/unknown, resume no-bounce를 검증한다.
- tampered prior candidate/selected/used_candidates 각각이 `malformed_prior_decision`인지 경계 테스트를 추가한다.

#### 중간 검증

```bash
export IOP_FORCE_GEMINI_TODAY=2026-07-26
python3 -m unittest agent-ops.skills.project.orchestrate-agent-task-loop.tests.test_select_execution_target.SelectorRouteMatrixTests agent-ops.skills.project.orchestrate-agent-task-loop.tests.test_select_execution_target.SelectorFailoverContractTests -v
```

예상 결과: canonical 주야간 전환과 tampered prior fail-closed가 모두 PASS한다.

### [REVIEW_REVIEW_REVIEW_REVIEW_API-3] 실제 budget/reopen과 completing-target selfcheck lifecycle

#### 문제

`test_dispatch.py:5346-5369`는 임의 Codex target을 budget helper에 직접 기록하고, `test_dispatch.py:5620-5647`은 production이 기록하지 않는 `worker_selfcheck_required`를 직접 주입한다. `dispatch.py:3744-3751`의 `run_worker()`는 완료 target의 cli/model과 `selfcheck_done`만 저장하므로 `task_requires_selfcheck()`가 정적 local-G08 fallback을 사용할 수 있다.

```python
# Before: dispatch.py:3744-3751
completed_spec = agent_spec_from_locator(locator) or spec
store.update_task(
    task,
    worker_done=True,
    worker_cli=completed_spec.cli,
    worker_model=completed_spec.model,
    selfcheck_done=not completed_spec.local_pi,
)
```

#### 해결 방법

`run_worker()` 성공 시 completing target의 `selfcheck_required`를 명시적으로 persist한다. dispatcher 테스트는 실제 canonical initial→failover invocation으로 worker budget 1+9, tenth block, retry-blocked reopen의 decision/history 보존, review stage 분리, success reset을 검증한다. Gemini→Laguna와 Laguna→Gemini locator를 각각 `run_worker()`에 통과시키고 Laguna 완료만 `run_selfcheck()`가 기존 pinned worker decision으로 실행되며 새 initial history를 만들지 않는지 검증한다.

```python
# After
store.update_task(
    task,
    worker_done=True,
    worker_cli=completed_spec.cli,
    worker_model=completed_spec.model,
    worker_selfcheck_required=completed_spec.local_pi,
    selfcheck_done=not completed_spec.local_pi,
)
```

#### 수정 파일 및 체크리스트

- [ ] `scripts/dispatch.py`: completing target selfcheck flag와 pinned worker decision resume를 일치시킨다.
- [ ] `tests/test_dispatch.py`: 실제 primary→alternate budget/reopen/history와 worker→selfcheck lifecycle을 검증한다.

#### 테스트 작성

- `DynamicFailoverBudgetTest`: primary 1회와 alternate 9회가 같은 worker budget/last_target/last_transition에 누적되고 reopen 뒤 tenth failure에서 차단되며 success만 reset하는지 검증한다.
- `DispatcherCanonicalFailoverIntegrationTest.test_completing_target_controls_selfcheck_and_reuses_pin`: `run_worker()`와 `run_selfcheck()`를 호출해 Laguna-only selfcheck, pin 재사용, no-new-initial history를 검증한다.

#### 중간 검증

```bash
export IOP_FORCE_GEMINI_TODAY=2026-07-26
python3 -m unittest agent-ops.skills.project.orchestrate-agent-task-loop.tests.test_dispatch.TaskStageTest agent-ops.skills.project.orchestrate-agent-task-loop.tests.test_dispatch.RouteDecisionPersistenceTest agent-ops.skills.project.orchestrate-agent-task-loop.tests.test_dispatch.DynamicFailoverBudgetTest agent-ops.skills.project.orchestrate-agent-task-loop.tests.test_dispatch.DispatcherCanonicalFailoverIntegrationTest -v
```

예상 결과: S07 stage counter와 S10 completing-target/pin evidence가 실제 entry point에서 PASS한다.

## 의존 관계 및 구현 순서

1. 항목 2에서 canonical prior invariant를 고정한다.
2. 항목 1에서 그 invariant를 사용하는 dispatcher failover 경계와 logical prompt를 구현한다.
3. 항목 3에서 완성된 transition을 budget/selfcheck lifecycle로 검증한다.

## 수정 파일 요약

| 파일 | 항목 |
