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

# Plan - Production retry handoff ordering과 actual invoke 회귀 복구

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

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

## 배경

이전 구현은 retry handoff id를 locator에 싣고 `commit_retry_handoff_locator()`로 소비하는 경로를 추가했지만, production `run_worker()`는 그 전에 `persisted_execution_decision()`을 실행한다. 이 decision commit이 pending/context를 먼저 지우므로 실제 `invoke()`는 handoff id를 보지 못하고 일반 active-locator update로 빠진다. focused restart test도 production `invoke()`를 호출하지 않고 본문을 복제한 fake를 직접 실행해 이 순서 결함을 숨겼다.

## Archive Evidence Snapshot

- Prior plan: `agent-task/m-agent-task-runtime-target-selector/11+10_unknown_isolation/plan_local_G06_11.log`
- Prior review: `agent-task/m-agent-task-runtime-target-selector/11+10_unknown_isolation/code_review_cloud_G06_11.log`
- Verdict: `FAIL`
- Required 1: `persisted_execution_decision()`의 `commit_execution_decision()`이 retry pending/context를 실제 `invoke()`보다 먼저 지워 새 locator에 handoff id가 기록되지 않고 atomic handoff commit이 호출되지 않는다.
- Required 2: save-fault test는 구형 locator-match helper만 검사하고 restart test는 production `invoke()` 본문을 복제한 fake를 직접 호출하므로 실제 publication/save-fault/restart 경로를 검증하지 않는다.
- 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 3개와 selected dispatcher 20개는 PASS, 전체 256개는 별도 후보 2개가 FAIL했다. `py_compile`과 `git diff --check`는 PASS했다. 실제 decision→production `invoke()` 순서 재현은 `context_after_decision=null`, 새 locator handoff id `null`, atomic commit 호출 수 `0`을 기록했다.
- Ownership/dependency: 기존 split subtask `11+10_unknown_isolation`과 testing domain 소유이며 신규 predecessor, 사용자 결정, 외부 권한은 없다.
- Roadmap carryover: `throughput-policy`, SDD S06/S09/S11의 persisted continuation, dispatch integration, same-batch quota snapshot 불변성을 유지한다.

## 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-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py`
- `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py`
- `agent-task/m-agent-task-runtime-target-selector/11+10_unknown_isolation/plan_local_G06_11.log`
- `agent-task/m-agent-task-runtime-target-selector/11+10_unknown_isolation/code_review_cloud_G06_11.log`
- `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/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` — dispatcher state에 매칭되는 계약 원문 없음.
- `agent-spec/index.md` — dispatcher에 매칭되는 living spec 없음.

### SDD 기준

- SDD는 `[승인됨]`, 구현 잠금은 `해제`이며 `결정 필요` 항목이 없다.
- S06 `context-failover`는 persisted logical continuation의 source artifact와 target identity를 요구하고, S09 `dispatch-integration`은 restart 가능한 dispatcher 상태 전이를 요구한다.
- S11 `admission evidence`는 retry task 수정이 같은 batch의 sibling quota snapshot과 일반 resume를 오염시키지 않아야 한다는 회귀 경계다.

### 테스트 환경 규칙

- `test_env=local`; local rules와 testing smoke profile을 적용한다.
- 실제 provider CLI와 네트워크는 호출하지 않는다. production `invoke()`는 그대로 실행하되 process 생성 seam만 deterministic fake/deny guard로 바꾼다.
- 이 Python state transaction은 Edge-Node dev script, mock smoke, live provider preflight 대상이 아니다.

### production 순서와 상태 경계

1. `mark_retry_quota_refresh()`가 stable handoff id를 포함한 pending/context를 저장한다.
2. `run_worker()`가 retry source locator를 resume input으로 복원한다.
3. `persisted_execution_decision()`이 failover/resume decision을 commit하되 handoff intent는 locator publication 전까지 보존해야 한다.
4. production `invoke()`가 최초 locator record에 handoff id를 포함해 durable write한다.
5. StateStore가 handoff id 일치 여부를 검증해 `active_locator` 갱신과 pending/context consume을 한 save로 commit한다.
6. commit 성공 뒤에만 provider process 생성 seam에 도달한다. save fault, mismatch, restart에서는 중복 process launch가 없어야 한다.

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

- `test_retry_handoff_consume_save_fault_preserves_pending`는 `commit_retry_handoff_locator()`의 실제 publication transaction을 검사하지 않는다.
- `test_retry_restart_preserves_handoff_and_consumes_pending`는 `dispatch.invoke`를 patch하면서 그 patch를 거치지 않고 local fake를 직접 호출한다.
- actual production call order, locator 최초 write, atomic StateStore save fault, StateStore 재개방, provider launch count를 한 회귀군에서 연결해야 한다.

### 심볼 참조

- 변경 후보: `StateStore.commit_retry_handoff_locator`, `commit_execution_decision`, `persisted_execution_decision`, `invoke`, `run_worker`.
- 참조 위치는 두 대상 Python 파일에 닫혀 있으며 구현 뒤 `rg --sort path`로 definition/call/test를 전부 재확인한다.

### 분할 판단

- split 정책을 검토했다. decision commit, locator publication, StateStore atomic consume, restart test는 하나의 ordered exactly-once invariant를 공유한다.
- production/test를 별도 child로 나누면 어느 한쪽도 독립 검증 가능한 `complete.log`를 만들 수 없으므로 기존 단일 subtask를 유지한다.

### 범위 결정 근거

- selector policy matrix, lane/G 정책, quota schema, Edge/Node 코드, roadmap/SDD/rules, `WORK_LOG.md`, unrelated dirty files는 수정하지 않는다.
- `ReviewControlTest.test_final_channel_contract_is_top_level_and_singular`와 `ThroughputQuotaBatchTest.test_retry_evidence_artifact_identity_variants`는 이번 Required 밖의 기존 후보다. 전체 suite에서는 이 두 실패 이외의 새 실패가 없는지만 확인한다.

### 최종 라우팅

- `evaluation_mode=isolated-reassessment`, `finalizer=finalize-task-policy.sh`, `finalizer_mode=pair`.
- build/review closures는 모두 true다. 범위는 두 Python 파일로 닫혔고, source/test/SDD와 실제 production-order 재현이 있으며 외부 권한이나 미결정 사항이 없다.
- build `route_basis=local-fit`, `capability_gap=none`, scores=`1/2/1/2/1`, loop-risk signatures=`temporal_state,concurrent_consistency,boundary_contract,variant_product`, `triggered=true`.
- review `route_basis=official-review`, `capability_gap=none`, scores=`1/2/1/2/1`, floor=`none`.
- finalizer output: build=`PLAN-local-G07.md`, review=`CODE_REVIEW-cloud-G07.md`, official target=`codex/gpt-5.6-sol xhigh`.

## 구현 체크리스트

- [ ] retry decision commit이 handoff intent를 actual locator publication 전까지 보존하고, 새 locator 기록과 pending/context consume이 provider spawn 전에 stable id 기반 StateStore commit으로 완료되도록 production 순서를 고친다.
- [ ] production `invoke()` 자체를 호출하는 deterministic success/save-fault/restart 회귀로 교체해 provider process 0회, StateStore 재개방 후 duplicate launch 0회, sibling quota state 불변을 증명한다.
- [ ] `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

### [REVIEW_REFACTOR-1] Production retry handoff ordering

- 문제: `dispatch.py:1695-1701`의 decision commit이 retry pending/context를 무조건 초기화하고 `:1769`에서 실제 `invoke()` 전에 호출된다. 따라서 `:3144-3156`의 stable-id locator commit은 production retry에서 도달할 수 없다.
- 해결 방법:
  - retry decision을 commit할 때 stable handoff context를 보존하는 명시적 상태 전이를 둔다. 일반 initial/resume/review commit의 기존 clear semantics는 유지한다.
  - `invoke()`가 StateStore에서 handoff id를 읽는 시점을 최초 locator write보다 앞으로 옮기고, 최초 record 자체에 id를 포함한다.
  - locator durable write 뒤 handoff id를 검증해 `active_locator`, pending flag, context를 한 StateStore save로 갱신한다. mismatch 또는 save fault이면 provider process를 시작하지 않고 pre-state를 보존한다.
  - restart pre-check는 locator record의 stable id와 pending context id가 일치할 때만 consume하며, legacy locator fallback은 기존 호환 범위를 넘겨 확대하지 않는다.
- 수정 파일 및 체크리스트:
  - [x] `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py`: decision commit 보존 조건, locator 최초 publication, atomic commit 및 provider-spawn guard.
- 테스트 작성: 작성. REVIEW_REFACTOR-2의 actual `invoke()` 회귀가 이 ordering의 성공·mismatch·save-fault·restart 상태를 검증한다.
- 중간 검증: `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py ThroughputQuotaBatchTest.test_retry_restart_preserves_handoff_and_consumes_pending -v` — PASS.

### [REVIEW_REFACTOR-2] Actual invoke/save-fault/restart regressions

- 문제: `test_dispatch.py:9536-9601`은 구형 helper만 fault 주입하고, `:9721-9803`은 production body를 복제한 fake를 직접 호출한다.
- 해결 방법:
  - test가 `dispatch.invoke()`를 직접 await하도록 하고 locator/state logic은 patch하지 않는다. `build_command` 또는 `asyncio.create_subprocess_exec` 같은 process 경계만 fake/deny한다.
  - process seam에 도달했을 때 새 locator의 handoff id, committed active locator, cleared pending/context를 관찰한다.
  - StateStore save fault에서는 호출이 process seam 전에 종료되고 in-memory/serialized retry state가 동일함을 검증한다.
  - StateStore를 닫아 재개방한 restart case에서 committed locator를 재사용하고 두 번째 provider launch가 없음을 검증한다.
  - 같은 batch의 normal sibling task decision/quota/history deep equality와 subprocess/provider call 0회를 함께 assert한다.
- 수정 파일 및 체크리스트:
  - [x] `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py`: helper-copy tests를 production-path matrix로 교체하고 provider deny guard를 추가한다.
- 테스트 작성: 작성. success, save-fault rollback, restart exactly-once를 최소 세 focused case로 유지한다.
- 중간 검증: `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py ThroughputQuotaBatchTest.test_retry_handoff_locator_consume_restart_windows ThroughputQuotaBatchTest.test_retry_handoff_consume_save_fault_preserves_pending ThroughputQuotaBatchTest.test_retry_restart_preserves_handoff_and_consumes_pending -v` — PASS (3/3).

## 의존 관계 및 구현 순서

1. retry-aware decision commit과 locator publication ordering을 수정한다.
2. production `invoke()` 기반 success/save-fault/restart 회귀를 작성한다.
3. focused, selected integration, full suite, compile/diff 검증 순으로 evidence를 갱신한다.

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `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.test_retry_handoff_locator_consume_restart_windows ThroughputQuotaBatchTest.test_retry_handoff_consume_save_fault_preserves_pending ThroughputQuotaBatchTest.test_retry_restart_preserves_handoff_and_consumes_pending -v` — production `invoke()` success/save-fault/restart, provider process 0회.
- `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py DynamicFailoverBudgetTest DispatcherCanonicalFailoverIntegrationTest BlockerDrainTest -v` — selected dispatcher regressions PASS.
- `python3 -m unittest discover -s agent-ops/skills/project/orchestrate-agent-task-loop/tests -p 'test_*.py'` — 이번 handoff 변경으로 새 실패 없음; 별도 후보 2개만 동일하게 남는다.
- `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.
- `rg -n --sort path 'commit_retry_handoff_locator|retry_quota_refresh_context|retry_handoff_id|persisted_execution_decision|asyncio.create_subprocess_exec' agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py` — definition/call/test가 계획 범위와 일치.
- `git diff --check` — 출력 없음, exit 0.

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