<!-- task=m-agent-task-runtime-target-selector/07+06_context_review_recovery plan=1 tag=TEST -->

# Plan - Dispatcher provider 격리 회귀 차단

## 배경

`plan=0`의 retry-blocked 상태 전이 검증은 header-only fixture로 `dispatch_with_store(..., dry_run=False)`를 호출하면서 runner seam을 mock하지 않았다. 이 때문에 실제 Pi worker가 시작됐고, fixture에는 수행할 작업이 없어 모델이 저장소를 임의로 탐색했다. 사용자 요청으로 중단된 실행의 PASS 출력은 신뢰하지 않는다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/automation-runtime-bridge/milestones/agent-task-runtime-target-selector.md`
- Task ids: `context-failover`, `failure-budget`, `dispatch-integration`, `audit-tests`
- Completion mode: check-on-pass

## Archive Evidence Snapshot

- `plan_local_G08_0.log`: 기존 구현 범위와 검증 계약. retry-blocked test의 real-provider escape 때문에 PASS 출력은 완료 근거로 사용하지 않는다.
- `code_review_cloud_G09_0.log`: 구현 evidence. 중단된 provider 실행과 공식 verdict 부재 때문에 새 완료 evidence로 재사용하지 않는다.
- context `feb6a8e1-1e52-4f76-8cb4-08a90e3e8869` 및 중단된 Pi test context들은 진단 근거일 뿐 resume 대상이 아니다.

## 분석 결과

### 읽은 파일

- `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py`
- `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py`
- `agent-ops/skills/project/orchestrate-agent-task-loop/SKILL.md`
- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-test/local/rules.md`
- `agent-roadmap/current.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-ops/skills/common/finalize-task-routing/SKILL.md`

### SDD 기준

승인·잠금 해제된 SDD의 S06/S07/S09/S12와 `context-failover`/`failure-budget`/`dispatch-integration`/`audit-tests`를 대상으로 한다. TEST-1은 persisted decision·stage budget·retry-blocked 상태만 fake runner 아래에서 검증하게 하며 provider 실행을 evidence에서 배제한다.

### 테스트 환경 규칙

`test_env=local`이고 `agent-test/local/rules.md`를 읽었다. 이 변경은 user-flow가 아닌 Python dispatcher simulation이므로 smoke profile을 적용하지 않는다. 검증은 focused/full unittest와 `py_compile`이며, provider CLI·session·network preflight는 의도적으로 실행하지 않는다.

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

- retry-blocked blocker/budget 초기화는 기존 test가 다루지만 runner 격리가 빠졌다.
- 새 dispatcher simulation에는 mock 누락을 즉시 실패시키는 class-level provider-deny guard가 없다.
- 실제 provider 동작은 범위 밖이며, 명시 사용자 승인 full-cycle 검증에서만 다룬다.

### 심볼 참조

이름 변경/삭제 없음. `dispatch_with_store`, `run_worker`, `run_escalating`, `invoke`, `build_command`의 test call path를 확인한다.

### 분할 판단

기존 `07+06_context_review_recovery`의 plan=1 후속이다. fixture guard와 plan rule은 같은 real-provider escape를 닫는 단일 test ownership boundary이므로 분리하지 않는다. `06+05_runtime_quota_failover` 완료 의존성은 유지한다.

### 범위 결정 근거

production `dispatch.py` route/selector policy, 실제 provider health, `WORK_LOG.md`, 과거 archive는 수정하지 않는다. test isolation과 project-local planning rule만 변경한다.

### 최종 라우팅

- `evaluation_mode=isolated-reassessment`, `finalizer=finalize-task-policy.sh`, `finalizer_mode=pair`.
- build/review closure는 scope/context/verification/evidence/ownership/decision 모두 `true`: scope가 test+project rule로 닫히고 deterministic unittest가 provider 없이 판정한다.
- build: `route_basis=local-fit`, `capability_gap=none`, scores=`1/2/1/2/1`, loop-risk audit=`temporal_state,boundary_contract`, `local-G07`, `PLAN-local-G07.md`.
- review: `route_basis=official-review`, `capability_gap=none`, scores=`1/2/1/2/1`, `cloud-G07`, `CODE_REVIEW-cloud-G07.md`, `codex/gpt-5.6-sol xhigh`.

## 구현 체크리스트

- [ ] provider-deny guard와 retry-blocked state-only fake를 추가해 provider command/session/process 0건을 검증한다.
- [ ] `testing` domain rule과 project domain map에 dispatcher simulation planning gate를 기록한다.
- [ ] focused/full unittest, py_compile, diff check의 실제 출력과 provider 0건 evidence를 review에 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

## 구현 항목

### [TEST-1] Dispatcher simulation provider-deny guard

- 문제: `test_context_budget_and_retry_blocked_lifecycle`가 `dispatch_with_store(..., dry_run=False)`를 호출하면서 `dispatch.py:4448` 이후의 실제 `invoke`까지 열어 Pi CLI를 시작했다.
- 해결 방법:

```python
# Before: test_dispatch.py:6698
with mock.patch.object(dispatch, "datetime") as datetime_mock:
    await dispatch.dispatch_with_store(args, workspace, store)
```

```python
# After
# asyncSetUp installs an invoke/build-command deny guard.
with mock.patch.object(dispatch, "scan_tasks", return_value=[]):
    await dispatch.dispatch_with_store(args, workspace, store)
```

  `SelectorDispatcherIntegrationTest`에 `invoke`와 provider command construction을 fail-fast로 막는 기본 guard를 설치한다. invocation semantics가 필요한 test만 deterministic fake locator를 반환하는 mock을 명시한다. retry-blocked는 state-only이므로 empty scan과 runner non-call assertion으로 blocker/budget 초기화 및 provider 0건을 함께 확인한다.
- 수정 파일 및 체크리스트:
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py`: class guard, retry-blocked fixture, runner/command non-call assertion.
- 테스트 작성: retry-blocked regression과 guard 없는 future runner path의 immediate failure를 검증한다.
- 중간 검증: `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py SelectorDispatcherIntegrationTest.test_context_budget_and_retry_blocked_lifecycle -v` — provider command/process 없이 PASS.

### [TEST-2] 계획 단계 provider 격리 규칙

- 문제: 기존 plan의 “fake runner input”은 서술뿐이어서 header-only fixture와 non-dry-run dispatcher를 실제 provider prompt에서 분리하지 못했다.
- 해결 방법: testing rule에 runner seam mock, empty scan state-only test, header-only fixture 금지, explicit full-cycle 외 provider 실행 금지를 추가하고 dispatcher scripts/tests를 domain map에 연결한다.
- 수정 파일 및 체크리스트:
  - [ ] `agent-ops/rules/project/domain/testing/rules.md`: dispatcher test isolation pattern/forbidden rule과 기준 commit 갱신.
  - [ ] `agent-ops/rules/project/rules.md`: dispatcher scripts/tests → testing mapping.
- 테스트 작성: 문서 rule 자체의 executable test는 생략한다. TEST-1 fail-fast guard가 runtime regression proof다.
- 중간 검증: `rg --sort path -n "provider-deny|header-only fixture|dispatch_with_store" agent-ops/rules/project agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py` — rule/guard 경로 출력.

## 의존 관계 및 구현 순서

1. TEST-1로 current suite의 real-provider escape를 먼저 차단한다.
2. TEST-2로 이후 계획에서 같은 dispatcher 경로가 testing rule을 읽게 한다.
3. focused와 전체 suite가 provider process 없이 PASS할 때만 review로 넘긴다.

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py` | TEST-1 |
| `agent-ops/rules/project/domain/testing/rules.md` | TEST-2 |
| `agent-ops/rules/project/rules.md` | TEST-2 |

## 최종 검증

- `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py SelectorDispatcherIntegrationTest.test_context_budget_and_retry_blocked_lifecycle -v` — fresh focused PASS, provider-deny guard invocation/command 0.
- `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py SelectorDispatcherIntegrationTest -v` — class simulation PASS, provider process/session 없음.
- `python3 -m unittest discover -s agent-ops/skills/project/orchestrate-agent-task-loop/tests -p 'test_*.py'` — fresh full suite PASS, external provider 미실행.
- `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`의 구현 에이전트 소유 섹션을 채운다.
