<!-- task=m-agent-task-runtime-target-selector/06+05_runtime_quota_failover plan=1 tag=REVIEW_REFACTOR -->

# Plan - Runtime quota snapshot과 cloud promotion state 정합성 보완

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

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

## 배경

첫 리뷰에서 `iop-node quota-probe`의 실제 JSON shape가 Python consumer에서 손실되고, live cloud promotion이 실행 target만 바꾼 채 persisted decision/history와 logical context를 갱신하지 않는 두 Required 결함이 확인됐다. producer 계약과 runtime state를 같은 canonical transition으로 맞추고 실제 cross-component fixture로 회귀를 닫는다.

## Archive Evidence Snapshot

- Prior task: `agent-task/m-agent-task-runtime-target-selector/06+05_runtime_quota_failover`
- Archived plan: `agent-task/m-agent-task-runtime-target-selector/06+05_runtime_quota_failover/plan_local_G08_0.log`
- Archived review: `agent-task/m-agent-task-runtime-target-selector/06+05_runtime_quota_failover/code_review_cloud_G09_0.log`
- Verdict: `FAIL`; Required 2, Suggested 0, Nit 0.
- Required summary: 실제 Go `QuotaSnapshot.targets[].status` 소비와 failover selected quota 보존이 누락됐고, cloud promotion이 persisted decision/history 및 logical context를 갱신하지 않았다.
- Affected files: `execution_target_policy.py`, `select_execution_target.py`, `dispatch.py`와 대응 Python tests. Go producer schema는 변경하지 않는다.
- Verification evidence: 계획된 Python 204 tests와 Go quota-probe tests는 PASS했지만, 실제 Go shape 최소 재현은 `exhausted → unknown`을 출력했다.
- Roadmap carryover: 이 pair에는 `Roadmap Targets`가 없으므로 PASS 시 Milestone Task 자동 체크를 주장하지 않는다.

## 분석 결과

### 읽은 파일

- `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`
- `apps/node/cmd/node/quota_probe.go`
- `apps/node/cmd/node/quota_probe_test.go`
- `apps/node/internal/adapters/cli/status/quota.go`
- `agent-test/local/rules.md`
- `agent-test/local/testing-smoke.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`

### SDD 기준

- SDD는 `[승인됨]`, 잠금은 `해제`다.
- S04/`quota-input` Evidence Map은 실제 Go usage checker snapshot의 required-cap tri-state와 work-unit admission을 요구한다.
- S06/`context-failover`, S07/`failure-budget`, S09/`dispatch-integration` Evidence Map은 qualified cross-target transition, 공유 stage budget, invocation/persisted decision/history 일치를 요구한다.
- 구현 체크리스트와 최종 검증은 실제 producer JSON fixture, selected quota round-trip, cloud promotion state/context/budget 통합 assertion으로 위 evidence를 직접 검증한다.

### 테스트 환경 규칙

- `test_env=local`; `agent-test/local/rules.md`와 `agent-test/local/testing-smoke.md`를 읽었다.
- testing-smoke의 Edge-Node full-cycle/live provider 명령은 agent-ops Python dispatcher와 hidden Go quota JSON bridge를 직접 실행하지 않아 이 follow-up의 pass criterion으로 사용하지 않는다.
- 현재 checkout에서 fresh-process Python `unittest`, `py_compile`, `git diff --check`와 `go test -count=1 ./apps/node/cmd/node -run 'QuotaProbe'`를 적용한다. 외부 provider/secret/service는 사용하지 않으며 test-rule 유지보수는 필요하지 않다.
- Go preflight는 `/config/.local/bin/go` → `/config/opt/go/bin/go`, `go1.26.2 linux/arm64`, `GOROOT=/config/opt/go`로 일치했다.

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

- `test_probe_candidate_quota_argv_and_normalization`은 실제 producer에 없는 top-level `status` fixture를 사용해 producer-consumer schema drift를 놓친다.
- runtime-probed cloud alternate를 선택한 failover에서 `candidates[].quota_status`와 top-level `quota`의 동일 snapshot identity/status를 검증하지 않는다.
- `test_cloud_g07_provider_quota_promotes_claude_to_codex_without_no_failover_block`은 invocation spec만 보고 persisted decision/history, logical context, stage budget을 검증하지 않는다.

### 심볼 참조

- `probe_candidate_quota`: `_candidate_quota`, `_failover`, selector tests에서 사용한다.
- `promoted_spec`: live `run_escalating`, legacy locator recovery, dry-run legacy recovery와 기존 tests에서 사용한다. live selector-backed promotion만 canonical transition으로 교체하고 legacy recovery call-site는 별도 근거 없이 제거하지 않는다.
- 새 dependency는 추가하지 않는다.

### 분할 판단

- 기존 split child `06+05_runtime_quota_failover`의 FAIL follow-up이며 `05+04_dispatch_route_source`는 `agent-task/archive/2026/07/m-agent-task-runtime-target-selector/05+04_dispatch_route_source/complete.log`로 충족됐다.
- 두 Required는 quota-selected candidate와 그 다음 runtime transition이 동일 persisted decision을 유지해야 한다는 하나의 state-machine invariant를 공유하고 selector/dispatcher 통합 테스트를 함께 갱신한다. nested subtask를 만들면 같은 transition schema와 fixture를 중복 변경하므로 단일 follow-up pair로 유지한다.
- sibling active artifacts와 dirty worktree는 사용자 소유로 보존하고 이 plan의 명시 파일 외 변경을 수정하지 않는다.

### 범위 결정 근거

- Go `QuotaSnapshot` producer schema, admission batch, selfcheck, target cap removal, unknown isolation, downstream audit child, roadmap 문서는 변경하지 않는다.
- agent-spec index에는 agent-task dispatcher에 매칭되는 living spec이 없고 agent-contract index에도 hidden quota-probe/runtime selector 계약 문서가 없다. 새 문서를 추정 생성하지 않고 승인 SDD, Go producer, 코드와 tests를 source of truth로 사용한다.

### 최종 라우팅

- evaluation_mode: `isolated-reassessment`; finalizer=`finalize-task-policy.sh`, finalizer_mode=`pair`.
- build closures: `scope_closed=true`(두 Required와 영향 경로 고정), `context_closed=true`(producer/selector/dispatcher와 tests를 함께 확인), `verification_closed=true`(exact-shape fixture와 focused/full tests), `evidence_trusted=true`(실제 shape 재현으로 실패 확인), `ownership_closed=true`(현재 child와 legacy recovery 경계 고정), `decision_closed=true`(승인 SDD와 기존 producer 계약으로 결정 가능).
- review closures: build와 같은 source/state/verification evidence로 모두 `true`.
- build route_basis=`grade-boundary`; capability_gap=`none`; scores(scope_coupling/state_concurrency/blast_irreversibility/evidence_diagnosis/verification_complexity)=`2/2/1/2/2`, final=`cloud-G09`, filename=`PLAN-cloud-G09.md`.
- review route_basis=`official-review`; capability_gap=`none`; scores=`2/2/1/2/2`, final=`cloud-G09`, filename=`CODE_REVIEW-cloud-G09.md`; target=`codex/gpt-5.6-sol xhigh`.
- loop-risk observations: ordered transitions 5개(initial/resume/failover/promotion/recovery), dispatcher/provider actors 2개와 persisted commit 제약, Go producer-selector-dispatcher 3-component JSON boundary, JSON/argv structured interpretation, status×transition variant product가 확인됐다. matched signatures=`temporal_state`, `concurrent_consistency`, `boundary_contract`, `structured_interpretation`, `variant_product`; triggered=`true`. signature는 lane/G에 영향을 주지 않았다.
- G floor: `none`; 반복 횟수와 기존 route는 입력과 판정에 사용하지 않았다.

## 구현 체크리스트

- [ ] 실제 Go `QuotaSnapshot.targets[].status`를 검증·보존하고 initial/failover selected quota가 같은 snapshot identity/status를 사용하도록 수정하며 exact-shape available/exhausted/unknown 회귀를 추가한다.
- [ ] qualified cloud promotion을 policy-owned canonical transition으로 실행해 logical context, persisted decision/history, invocation target과 공유 stage budget을 일치시키고 통합 회귀를 추가한다.
- [ ] focused Go/Python tests, 전체 Python suite, `py_compile`, `git diff --check`를 fresh output으로 검증한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

## 구현 항목

### [REVIEW_REFACTOR-1] Go quota snapshot consumer와 selected quota 정합성

- 문제: `select_execution_target.py:421`은 실제 producer에 없는 top-level `status`를 읽어 `targets[].status=available|exhausted`를 `unknown`으로 바꾼다. `_failover`의 `select_execution_target.py:865-895`는 직접 probe한 alternate snapshot을 선택 후 top-level `quota`에 전달하지 않는다.
- 해결 방법:

```python
# Before: select_execution_target.py:421
status = data.get("status")
return {"snapshot_id": data.get("snapshot_id"), "targets": [{"status": status}]}
```

```python
# After
snapshot = _validate_quota_snapshot(data)
status = _snapshot_status(candidate_identity(adapter, target), snapshot)
return snapshot
```

  producer의 `snapshot_id`, `source`, `checked_at`, `targets`를 보존하고 요청 adapter/target에 매칭되는 tri-state만 소비한다. `_failover`는 candidate별 probed snapshot을 보관해 선택된 cloud alternate의 `_selected_quota(..., probed_snapshot=selected_snapshot)`에 전달한다. subprocess 오류/timeout/parse/schema mismatch만 target-local `unknown`으로 정규화한다.
- 수정 파일 및 체크리스트:
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/select_execution_target.py`: actual snapshot validation/preservation과 selected failover snapshot 연결.
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_select_execution_target.py`: 실제 Go shape available/exhausted/unknown 및 alternate round-trip 회귀.
- 테스트 작성: `SelectorFailoverContractTests` 또는 quota 전용 class에 실제 `QuotaSnapshot` shape를 반환하는 fake subprocess를 두고 exhausted initial rejection/fallback, available selection, malformed→unknown, failover candidate/top-level quota 동일성을 assert한다.
- 중간 검증: `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_select_execution_target.py SelectorFailoverContractTests SelectorQuotaRepresentationTests -v`와 `go test -count=1 ./apps/node/cmd/node -run 'QuotaProbe'`가 PASS해야 한다.

### [REVIEW_REFACTOR-2] Cloud promotion canonical transition과 logical context commit

- 문제: `dispatch.py:3299-3326`은 live cloud promotion에서 `spec` 지역 변수만 바꿔 실제 invocation이 `execution_decisions[stage]` 및 `route_transition_history`와 어긋나고 cross-adapter context package를 검증하지 않는다.
- 해결 방법:

```python
# Before: dispatch.py:3299
next_spec = promoted_spec(spec, codex_recovery_count)
spec = next_spec
previous_locator = locator
```

```python
# After
next_decision = select_execution_decision(
    task, stage=role, prior_decision=current_decision,
    transition="promotion", failure_class=failure,
)
context = build_context_package(
    workspace, task, locator, previous_spec=spec,
    next_spec=agent_spec_from_decision(next_decision),
)
commit_execution_decision(store, task, role, next_decision)
```

  `execution_target_policy.py`에 initial matrix와 분리된 canonical cloud promotion chain을 선언하고 selector가 qualified failure, used target, no-bounce를 검증하는 `promotion` transition을 반환하게 한다. `agent_spec_from_decision`은 policy-owned promotion target만 허용한다. live selector-backed path는 context package 검증 후 decision/history를 commit하고 다음 invocation을 시작한다. `promoted_spec`은 필요한 legacy locator recovery에만 남긴다.
- 수정 파일 및 체크리스트:
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/execution_target_policy.py`: canonical promotion target/chain.
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/select_execution_target.py`: promotion transition schema와 validation.
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py`: live promotion context/commit orchestration.
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_execution_target_policy.py`: promotion matrix.
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_select_execution_target.py`: qualified promotion, used/no-bounce와 schema tamper.
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py`: actual invocation, decision/history, context evidence, shared budget integration.
- 테스트 작성: 기존 cloud G07 promotion test를 보강해 Claude→Codex target, `transition.trigger`, selected target, history, PLAN/locator/raw/normalized/workspace prompt와 stage budget count/reset을 assert한다. AGY→Claude→Codex와 unqualified failure same-target/no-bounce case를 table-driven으로 추가한다.
- 중간 검증: `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py DispatcherCanonicalFailoverIntegrationTest DynamicFailoverBudgetTest -v`가 PASS해야 한다.

## 의존 관계 및 구현 순서

1. `REVIEW_REFACTOR-1`로 runtime quota evidence를 신뢰할 수 있게 만든다.
2. `REVIEW_REFACTOR-2`가 그 candidate/transition identity를 사용해 cloud promotion state를 commit한다.
3. 전체 회귀로 legacy recovery와 local failover가 유지되는지 확인한다.

## 수정 파일 요약

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

## 최종 검증

- `go test -count=1 ./apps/node/cmd/node -run 'QuotaProbe'` — actual producer contract PASS; fresh run required.
- `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_select_execution_target.py SelectorFailoverContractTests SelectorQuotaRepresentationTests -v` — producer shape, tri-state와 selected quota PASS.
- `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py DispatcherCanonicalFailoverIntegrationTest DynamicFailoverBudgetTest -v` — promotion state/context/budget PASS.
- `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` — 출력 없음.

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