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

# Deterministic route evidence와 실제 failover lifecycle 검증 보완

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

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

## 배경

직전 구현은 canonical selected/used history와 logical context failover commit 원자성을 보완했지만, 승인 selector 입력에 없는 환경변수가 KST 야간 route를 바꾸고 persisted decision의 canonical 정책 필드 변조가 수용된다. 또한 S07 budget과 S10 completing-target 테스트는 계획에서 요구한 production entry point와 lifecycle assertion을 끝까지 실행하지 않는다. 이 후속 작업은 숨은 입력을 제거하고 실제 primary→alternate/reopen/success 및 양방향 selfcheck 증거를 닫는다.

## Archive Evidence Snapshot

- 직전 계획: `agent-task/m-agent-task-runtime-target-selector/04+03_failover_budget/plan_local_G08_16.log`.
- 직전 리뷰: `agent-task/m-agent-task-runtime-target-selector/04+03_failover_budget/code_review_cloud_G08_16.log` — FAIL, Required 4 / Suggested 0 / Nit 0.
- 영향 파일: `execution_target_policy.py`, `select_execution_target.py`, `test_execution_target_policy.py`, `test_select_execution_target.py`, `test_dispatch.py`.
- 검증 evidence: focused selector 9, dispatcher 23, lifecycle 28, 전체 192 tests와 `py_compile`/`git diff --check`는 PASS했다. 별도 재현은 동일 고정 입력이 `IOP_FORCE_GEMINI_TODAY` 유무에 따라 Laguna/Gemini로 갈리고, `time_window`/`rule_id`/`reason_codes` 변조 prior가 모두 수용됨을 확인했다.
- 닫힌 carryover: logical context artifact 검증 실패 전 decision/history commit 문제는 구현과 integration test로 닫혔다.
- 남은 carryover: S01/S02/S07/S10과 `time-route`, `context-failover`, `failure-budget`, `selfcheck-policy` evidence다.

## 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`: canonical persisted decision과 Gemini↔Laguna logical 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-ops/skills/project/orchestrate-agent-task-loop/SKILL.md`
- `agent-test/local/rules.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는 `[승인됨]`, 구현 잠금 해제 상태다.
- S01/S02는 명시 입력에 대해 결정적인 output과 KST `[07:00,23:00)` Gemini / `[23:00,07:00)` Laguna 최초 route를 요구한다.
- S06은 persisted decision과 logical failover transition evidence가 canonical identity를 유지해야 한다.
- S07은 같은 stage에서 primary/alternate를 합쳐 10회 budget을 쓰고 success에서만 reset해야 한다.
- S10은 실제 completing target이 Laguna일 때만 selfcheck를 요구한다.
- `test_env=local`; 별도 profile route가 없어 deterministic `unittest`, `py_compile`, `rg`, `git diff --check`로 검증한다.

### 원인 및 커버리지 공백

- `execution_target_policy.py:94-104`의 `IOP_FORCE_GEMINI_TODAY`는 selector contract 밖의 hidden input이며 야간 policy를 덮어쓴다.
- `select_execution_target.py:589-650`은 canonical candidates만 비교하고 `rule_id`, `policy_priority`, `reason_codes`, `time_window`를 비교하지 않으며 invalid/naive `evaluated_at`을 현재 시각으로 대체한다.
- `test_dispatch.py:5346-5424`는 첫 invocation sequence/history와 reopen invocation 수·spec, `last_transition`을 assertion하지 않고 success reset test가 없다.
- `test_dispatch.py:5746-5796`는 `run_escalating()` 성공과 failover decision을 직접 주입해 실제 Gemini→Laguna, Laguna→Gemini, cloud completion을 관찰하지 않는다.

### 심볼 참조

- 제거 대상: `IOP_FORCE_GEMINI_TODAY`, `worker-local-g07-g08-manual-gemini`, `manual_today_gemini`, `manual-gemini-today`와 해당 전용 테스트.
- 변경 call sites: `select_policy()`의 local G07/G08 branch, `_validate_prior_candidate_identity()`의 resume/failover callers, `run_escalating()`/`run_worker()`/`run_selfcheck()`를 사용하는 dispatcher integration fixtures.
- production dispatcher 변경은 현재 Required가 아니며 새 실제 lifecycle 테스트가 별도 correctness 오류를 드러내지 않는 한 수정하지 않는다.

### 분할 판단

- split policy를 재평가했다. persisted selector evidence, stage budget, completing-target selfcheck는 같은 `execution_decisions[stage]`, `route_transition_history`, `stage_failure_budgets` lifecycle을 공유한다.
- 별도 ownership, deployment, UI, 외부 validation 환경 경계가 없고 현재 dependent subtask의 PASS가 네 roadmap target을 함께 닫으므로 단일 follow-up pair를 유지한다.
- predecessor 상태는 직전 active pair가 명시한 evidence를 승계하며 추가 archive 탐색은 하지 않는다.

### 범위 결정 근거

- KST canonical matrix, quota tri-state, logical context package와 commit 원자성, cloud legacy promotion은 회귀 대상으로만 유지한다.
- roadmap/SDD, 공통 agent-ops 규칙, 계약/spec, dependency는 변경하지 않는다.
- production 수정은 policy hidden input 제거와 prior canonical evidence validation으로 제한한다. S07/S10은 기존 production entry point를 관찰하는 테스트 evidence로 닫는다.

### 최종 라우팅

- `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(initial/resume/failover/reopen/success), concurrent_consistency=false, boundary_contract=true(policy/selector/dispatcher/state), structured_interpretation=true(persisted JSON), variant_product=true(환경×시간×전환 및 completion target×stage), `triggered=true`; unknown 없음.
- Review closures: scope/context/verification/evidence/ownership/decision 모두 `true`. Review scores는 2/2/1/2/1이며 `route_basis=official-review`, lane=`cloud`, grade=`G08`, filename=`CODE_REVIEW-cloud-G08.md`, target=`codex/gpt-5.6-sol xhigh`.
- capability-gap과 grade floor는 none이다. 이전 lane/grade와 반복 횟수는 격리 라우팅 입력에서 제외했다.

## 구현 체크리스트

- [ ] REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-1 hidden route input을 제거하고 persisted canonical decision evidence 전체를 fail-closed 검증한다.
- [ ] REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-2 실제 primary 1 + alternate 9, reopen, audit, stage 분리와 success reset을 검증한다.
- [ ] REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-3 실제 Gemini→Laguna, Laguna→Gemini, cloud completion에서 completing-target selfcheck lifecycle을 검증한다.
- [ ] `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 변경 내용과 검증 출력으로 채운다.

### [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-1] Deterministic route와 canonical decision evidence

#### 문제

명시되지 않은 process 환경변수가 같은 selector 입력의 output을 바꾸며, resume/failover는 candidate identity만 맞으면 persisted policy metadata 변조를 수용한다. invalid `evaluated_at`의 현재 시각 fallback도 결정성과 fail-closed 계약을 위반한다.

#### 해결 방법

local G07/G08 route는 오직 aware `evaluated_at`의 KST window로 선택한다. manual override rule, enum과 환경 조작 validation을 제거한다. prior `decision.evaluated_at`은 aware ISO로 parse되지 않으면 거부하고, 그 시각으로 재계산한 canonical `rule_id`, `policy_priority`, `reason_codes`, `time_window`, candidates를 persisted evidence와 모두 비교한다.

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

- [ ] `scripts/execution_target_policy.py`: hidden override와 불필요한 `os` 의존성을 제거한다.
- [ ] `scripts/select_execution_target.py`: manual enum/env branch를 제거하고 full canonical decision evidence 및 aware evaluated-at 검증을 추가한다.
- [ ] `tests/test_execution_target_policy.py`: manual override 전용 테스트를 제거하고 관련 없는 환경 변화에도 고정 route가 동일함을 검증한다.
- [ ] `tests/test_select_execution_target.py`: policy metadata 및 invalid/naive evaluated-at tamper variants를 resume/failover에서 거부한다.

#### 테스트 작성

- policy fixed-clock test는 임의 unrelated environment 변화 전후 candidate/rule/time-window가 동일하고 02:00 KST가 Laguna인지 확인한다.
- selector tamper table은 `rule_id`, `policy_priority`, `reason_codes`, `time_window`, invalid/naive `evaluated_at`을 각각 `malformed_prior_decision`으로 닫고 valid cross-boundary resume은 유지한다.

#### 중간 검증

```bash
env -u IOP_FORCE_GEMINI_TODAY python3 -m unittest agent-ops.skills.project.orchestrate-agent-task-loop.tests.test_execution_target_policy agent-ops.skills.project.orchestrate-agent-task-loop.tests.test_select_execution_target.SelectorContractTests 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
```

예상 결과: 고정 clock route가 environment와 무관하고 canonical evidence tamper만 fail closed한다.

### [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-2] S07 실제 budget/reopen/reset evidence

#### 문제

현재 reopen 테스트는 counter 최종값만 확인하며 actual invocation/history/audit 일부를 읽고도 assertion하지 않는다. success가 worker budget만 reset하고 review stage를 보존한다는 계획 테스트도 없다.

#### 해결 방법

첫 `run_escalating()`에서 Gemini primary 실패와 Laguna alternate 첫 실패를 실제 selector failover로 만들고 invocation `[Gemini, Laguna]`, history `[initial, failover]`, count 2를 확인한다. store reopen 뒤 Laguna 8회를 더 실패시켜 총 `[Gemini 1, Laguna 9]`, count 10, `last_target`, `last_transition`, unchanged decision/history, review count 0을 검증한다. 별도 fixture에서 worker success 후 worker count만 0이 되고 pre-existing review count는 유지되는지 확인한다.

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

- [ ] `tests/test_dispatch.py`: reopen 전후 invocation sequence/count와 route history를 assertion한다.
- [ ] `tests/test_dispatch.py`: failure budget audit fields, stage 분리, success-reset-only-current-stage 테스트를 추가한다.

#### 테스트 작성

- `DynamicFailoverBudgetTest.test_primary_and_alternate_share_budget_across_reopen`
- `DynamicFailoverBudgetTest.test_success_resets_only_current_stage_budget`

#### 중간 검증

```bash
env -u IOP_FORCE_GEMINI_TODAY python3 -m unittest agent-ops.skills.project.orchestrate-agent-task-loop.tests.test_dispatch.DynamicFailoverBudgetTest agent-ops.skills.project.orchestrate-agent-task-loop.tests.test_dispatch.RouteDecisionPersistenceTest -v
```

예상 결과: production selector transition을 통한 1+9/reopen/audit와 stage-local reset이 PASS한다.

### [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-3] S10 실제 completing-target selfcheck evidence

#### 문제

현재 테스트는 production escalation을 mock으로 우회하고 synthetic failover decision을 주입한다. 따라서 실제 completing target과 selfcheck 결정의 연결을 증명하지 않는다.

#### 해결 방법

`run_worker()`와 실제 `run_escalating()`은 유지하고 provider `invoke()`만 deterministic mock한다. day Gemini→Laguna success, night Laguna→Gemini success, cloud target success를 독립 fixture/table로 실행한다. Laguna completion만 selfcheck stage로 이동하고 pinned Laguna로 `run_selfcheck()`를 실행하며, Gemini/cloud completion은 selfcheck 없이 review로 이동해야 한다. 각 case의 invocation specs, decision selected/used history와 transition sequence를 확인해 새로운 duplicate `initial`이 생기지 않음을 검증한다.

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

- [ ] `tests/test_dispatch.py`: 세 actual completion variant를 production worker entry point로 실행한다.
- [ ] `tests/test_dispatch.py`: Laguna case selfcheck spec과 Gemini/cloud skip, pinned resume/no-new-initial history를 assertion한다.

#### 테스트 작성

- `DispatcherCanonicalFailoverIntegrationTest.test_completing_target_controls_selfcheck_and_reuses_pin`을 실제 invocation 기반 table/fixture로 재작성한다.

#### 중간 검증

```bash
env -u IOP_FORCE_GEMINI_TODAY 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.TaskStageTest -v
```

예상 결과: 세 completion variant 중 Laguna completion만 pinned Laguna selfcheck를 실행한다.

## 의존 관계 및 구현 순서

1. 항목 1에서 policy 입력과 persisted decision canonical evidence를 고정한다.
2. 항목 2에서 그 canonical transition을 실제 failure budget lifecycle로 검증한다.
3. 항목 3에서 같은 transition의 completing target이 selfcheck stage를 결정함을 검증한다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/execution_target_policy.py` | REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-1 |
| `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/select_execution_target.py` | REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-1 |
| `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_execution_target_policy.py` | REVIEW_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_REVIEW_API-1 |
| `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py` | REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-2, REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-3 |

## 최종 검증

```bash
env -u IOP_FORCE_GEMINI_TODAY python3 -m unittest agent-ops.skills.project.orchestrate-agent-task-loop.tests.test_execution_target_policy agent-ops.skills.project.orchestrate-agent-task-loop.tests.test_select_execution_target.SelectorContractTests 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
env -u IOP_FORCE_GEMINI_TODAY python3 -m unittest agent-ops.skills.project.orchestrate-agent-task-loop.tests.test_dispatch.DynamicFailoverBudgetTest agent-ops.skills.project.orchestrate-agent-task-loop.tests.test_dispatch.RouteDecisionPersistenceTest -v
env -u IOP_FORCE_GEMINI_TODAY 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.TaskStageTest -v
env -u IOP_FORCE_GEMINI_TODAY python3 -m unittest discover -s agent-ops/skills/project/orchestrate-agent-task-loop/tests -p 'test_*.py' -v
python3 -m py_compile 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
! rg -n "IOP_FORCE_GEMINI_TODAY|manual-gemini-today|manual_today_gemini|worker-local-g07-g08-manual-gemini" agent-ops/skills/project/orchestrate-agent-task-loop/scripts agent-ops/skills/project/orchestrate-agent-task-loop/tests
git diff --check
```

예상 결과: focused policy/selector/budget/selfcheck와 전체 suite가 모두 PASS하고 hidden override match, compile, diff 오류가 없다. 테스트 cache는 Python `unittest`에 해당하지 않는다.

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다.
