<!-- task=m-agent-task-runtime-target-selector/11+10_unknown_isolation plan=1 tag=REVIEW_REFACTOR -->

# Plan - Retry-blocked quota refresh와 probe 격리 보완

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

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

## 배경

첫 리뷰에서 일반 resume과 새 generation은 구분됐지만 `--retry-blocked`가 같은 generation의 persisted decision 때문에 quota probe를 건너뛰는 결함이 확인됐다. 또한 resume 회귀 테스트의 최초 cloud decision이 fake 밖에서 실제 quota-probe subprocess 경로를 열어 검증이 호스트 상태에 의존했다. Pinned route와 transition history를 보존하면서 명시적 retry만 새 task-local quota evidence를 관측하도록 수명 경계를 닫는다.

## Archive Evidence Snapshot

- Prior plan: `agent-task/m-agent-task-runtime-target-selector/11+10_unknown_isolation/plan_local_G07_0.log`
- Prior review: `agent-task/m-agent-task-runtime-target-selector/11+10_unknown_isolation/code_review_cloud_G07_0.log`
- Verdict: `FAIL`
- Required:
  - `--retry-blocked` 뒤 persisted worker decision이 admission probe를 차단해 `probe_calls=0`, `batch_snapshot=None`이 되는 계약 누락.
  - `test_same_work_unit_resume_zero_probe_and_pin_preserved` 최초 decision이 실제 `iop-node quota-probe` subprocess 경로를 여는 검증 격리 누락.
- Suggested: 없음
- Nit: 없음
- Affected files: `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py`, `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py`
- Verification evidence: focused reproducer는 retry clear 뒤 fresh probe 부재와 resume test의 `subprocess.run` 1회 호출을 재현했고, 기존 `ThroughputQuotaBatchTest` 10개, `BlockerDrainTest` 6개, 전체 Python suite 249개는 PASS했다.
- Roadmap carryover: `throughput-policy`와 SDD S11의 persisted route pin, batch quota snapshot, unknown 격리 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:
  - `throughput-policy`: target별 정적 cap 없이 ready task dispatch와 batch quota snapshot 재사용
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-task/m-agent-task-runtime-target-selector/11+10_unknown_isolation/plan_local_G07_0.log`
- `agent-task/m-agent-task-runtime-target-selector/11+10_unknown_isolation/code_review_cloud_G07_0.log`
- `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.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_select_execution_target.py`
- `agent-ops/rules/project/domain/testing/rules.md`
- `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: `agent-roadmap/sdd/automation-runtime-bridge/agent-task-runtime-target-selector/SDD.md`, 상태 `[승인됨]`, 잠금 `해제`.
- 대상 Acceptance Scenario: S11 → Milestone Task `throughput-policy`.
- Evidence Map: 동일 target 무상한 admission, batch snapshot 재사용, 조회 실패의 task/batch-local unknown 격리.
- SDD state machine의 `retry_blocked`는 blocker와 stage budget만 초기화하고 route history를 보존하며, quota exhaustion으로 대기한 failover eligibility만 새 snapshot으로 재평가할 수 있어야 한다. 이 조건을 구현 체크리스트와 focused retry 회귀에 반영한다.

### 테스트 환경 규칙

- `test_env=local`; `agent-test/local/rules.md`와 testing domain rule을 읽었다.
- `agent-test/local/testing-smoke.md`는 Edge-Node dev/smoke 범위이므로 agent-task dispatcher 내부 상태 회귀를 직접 검증하지 않는다. 외부 Edge/Node, provider, network를 실행하지 않고 dispatcher/selector의 가장 높은 seam을 deterministic fake로 대체한다.
- 저장소 Python test manifest를 직접 근거로 focused `unittest`, 전체 discovery, `py_compile`, `git diff --check`를 사용한다.
- 실제 provider CLI, quota probe subprocess, network 호출은 테스트에서 금지하고 mock 호출 횟수로 격리를 증명한다.
- 리뷰 시점 기준 전체 Python suite는 fresh process 249 tests PASS다.

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

- 기존 `test_context_budget_and_retry_blocked_lifecycle`는 blocker/budget/decision 보존만 확인하고 retry 뒤 새 quota snapshot 관측을 검증하지 않는다.
- 기존 `test_same_work_unit_resume_zero_probe_and_pin_preserved`는 최초 cloud decision을 fake 밖에서 만들어 실제 quota-probe subprocess seam을 연다.
- 일반 resume 0 probe, 새 generation fresh batch, task-local derived exhausted와 batch-key unknown은 기존 테스트가 커버하므로 보존 회귀만 필요하다.

### 심볼 참조

- rename/remove 없음.
- 영향 호출 경계: `StateStore.clear_blocked`, `has_persisted_worker_decision`, `build_admission_batch_snapshot`, `dispatch_with_store`, `run_worker`, `run_escalating`.

### 분할 판단

- split decision policy를 먼저 평가했다. 이 follow-up은 기존 `11+10_unknown_isolation` child 안에서 retry intent의 snapshot 수명과 같은 테스트 클래스의 격리를 함께 닫는 단일 persisted-state 경계다.
- shared API/광범위 call-site rollout, 별도 domain, 독립 배포, 상이한 검증 profile이 없고 둘을 분리하면 retry snapshot의 실제 소비 증거가 끊기므로 추가 split을 만들지 않는다.
- 선행 index `10`은 `agent-task/archive/2026/07/m-agent-task-runtime-target-selector/10+09_target_cap_removal/complete.log`로 충족됐다.

### 범위 결정 근거

- `select_execution_target.py`의 quota schema와 derived evidence helper는 현재 회귀를 통과하므로 변경하지 않는다.
- route matrix, target cap 제거, provider adapter, global cache, roadmap/SDD 문서는 변경하지 않는다.
- pinned decision과 `route_transition_history`를 삭제하거나 새 initial route로 바꾸는 방식은 SDD retry 계약을 위반하므로 금지한다.

### 최종 라우팅

- evaluation_mode: `isolated-reassessment`.
- finalizer=`finalize-task-policy.sh`, finalizer_mode=`pair`; build/review status=`routed`.
- build closures: `scope_closed=true`(두 Required와 영향 경계가 고정됨), `context_closed=true`(dispatcher state/admission/worker와 같은 테스트 파일로 닫힘), `verification_closed=true`(focused reproducer와 전체 suite가 결정적임), `evidence_trusted=true`(follow-up은 모든 provider/probe seam을 fake로 차단함), `ownership_closed=true`(dispatcher task-local state만 수정), `decision_closed=true`(승인 SDD retry 계약으로 결정 가능).
- review closures: `scope_closed=true`, `context_closed=true`, `verification_closed=true`, `evidence_trusted=true`, `ownership_closed=true`, `decision_closed=true`.
- build route_basis=`local-fit`; capability_gap=`none`.
- build loop-risk observations: ordered transitions=`blocked → retry-refresh-pending → pinned resume/failover`; concurrent actors=`scheduler admission + worker`; boundary components=`StateStore + batch provider + worker/failover`; variant axes=`ordinary resume / explicit retry / new generation`. matched signatures=`temporal_state`, `concurrent_consistency`, `boundary_contract`, `variant_product`; 이는 lane/G에 영향을 주지 않는다.
- build scores(scope_coupling/state_concurrency/blast_irreversibility/evidence_diagnosis/verification_complexity)=`1/2/1/1/2`, final=`local-G07`, filename=`PLAN-local-G07.md`.
- review route_basis=`official-review`; official execution=`codex/gpt-5.6-sol xhigh`; capability_gap=`none`.
- review scores(scope_coupling/state_concurrency/blast_irreversibility/evidence_diagnosis/verification_complexity)=`1/2/1/1/2`, final=`cloud-G07`, filename=`CODE_REVIEW-cloud-G07.md`.

## 구현 체크리스트

- [x] 명시적 `--retry-blocked`는 pinned worker decision과 route history를 보존하면서 해당 task의 quota refresh intent를 한 번만 소비해 fresh task-local snapshot으로 failover eligibility를 재평가하며, 일반 resume은 계속 0 probe를 유지한다.
- [x] quota/retry 회귀는 모든 provider runner와 quota subprocess seam을 deterministic fake로 차단하고 explicit retry, ordinary resume, 새 generation의 서로 다른 snapshot 수명을 assertion한다.
- [x] `ThroughputQuotaBatchTest`, `BlockerDrainTest`, 전체 Python suite, `py_compile`, `git diff --check`를 실행해 기존 unknown 격리와 독립 branch drain을 보존한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

## 구현 항목

### [REVIEW_REFACTOR-1] Retry quota snapshot 수명 보정

- 문제: `dispatch.py:562`의 `clear_blocked`는 retry intent를 남기지 않고, `dispatch.py:1617`의 admission은 같은 work-unit decision이 있으면 항상 probe를 건너뛴다. 그 결과 명시적 retry도 일반 resume과 같아져 fresh snapshot을 얻지 못한다.
- 해결 방법:

```python
# Before: agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py:562,1617
store.clear_blocked(args.task_group)
if has_persisted_worker_decision(state, task):
    continue
```

```python
# After
store.mark_retry_quota_refresh(args.task_group)
if has_persisted_worker_decision(state, task) and not retry_quota_refresh_pending(state):
    continue
# Persist one fresh task-local snapshot for retry failover eligibility,
# then clear the refresh marker without replacing the pinned decision/history.
```

- 수정 파일 및 체크리스트:
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py`: blocker가 실제로 해제된 worker task에 task-local one-shot refresh intent를 기록하고, admission batch에서 persisted route skip의 명시적 retry 예외를 처리한다.
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py`: fresh snapshot을 해당 task의 retry/failover evidence로 저장한 뒤 intent를 원자적으로 소비하며 pinned decision과 transition history는 유지한다.
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py`: `test_context_budget_and_retry_blocked_lifecycle` 또는 명시적 named 회귀에서 retry 1회 probe, fresh snapshot identity, decision/history 보존, 다음 ordinary resume 0 probe를 검증한다.
- 테스트 작성: 작성. fake batch probe와 fake runner를 사용해 `--retry-blocked`에서만 refresh가 발생하고 독립 task와 일반 resume에는 전파되지 않음을 검증한다.
- 중간 검증: `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py ThroughputQuotaBatchTest SelectorDispatcherIntegrationTest.test_context_budget_and_retry_blocked_lifecycle -v` — retry snapshot 수명과 기존 throughput 회귀 PASS.

### [REVIEW_REFACTOR-2] Resume 테스트의 quota probe subprocess 차단

- 문제: `test_dispatch.py:8741`의 최초 cloud decision이 deterministic quota evidence 없이 생성돼 production `subprocess.run`을 실제 호출한다. command 미설치 예외가 unknown으로 정규화되어 테스트는 우연히 PASS한다.
- 해결 방법:

```python
# Before: agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py:8741
init_d, _ = dispatch.persisted_execution_decision(store, t1, stage="worker")
```

```python
# After
with mock.patch.object(selector.subprocess, "run", side_effect=AssertionError("unexpected quota subprocess")) as run:
    init_d, _ = dispatch.persisted_execution_decision(
        store, t1, stage="worker", quota_snapshot=deterministic_snapshot
    )
run.assert_not_called()
```

- 수정 파일 및 체크리스트:
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py`: 최초 decision과 resume 모두 deterministic snapshot/fake 아래 실행한다.
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py`: 실제 quota subprocess와 provider runner가 호출되지 않았음을 명시적으로 assertion한다.
- 테스트 작성: 기존 resume 회귀를 강화한다. 별도 test-only 파일은 만들지 않는다.
- 중간 검증: `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py ThroughputQuotaBatchTest.test_same_work_unit_resume_zero_probe_and_pin_preserved -v` — subprocess 0회, resume batch probe 0회, pin 유지 PASS.

## 의존 관계 및 구현 순서

1. REVIEW_REFACTOR-1에서 retry refresh state와 소비 경계를 구현한다.
2. REVIEW_REFACTOR-2에서 host-dependent probe를 제거하고 ordinary resume 대조군을 고정한다.
3. 전체 회귀로 unknown 격리, blocker drain, 기존 selector state를 확인한다.

## 수정 파일 요약

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

## 최종 검증

- `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py ThroughputQuotaBatchTest SelectorDispatcherIntegrationTest.test_context_budget_and_retry_blocked_lifecycle -v` — explicit retry/ordinary resume/new generation snapshot 수명과 unknown 격리 PASS.
- `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py BlockerDrainTest -v` — 독립 branch drain 회귀 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/tests/test_dispatch.py` — exit 0.
- `git diff --check` — 출력 없음.

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