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

# Plan - retry handoff locator 원자성 및 production restart 회귀 복구

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

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

## 배경

현재 retry handoff는 locator를 두 번 기록해 첫 durable record에 stable handoff ID가 없다. 또한 restart regression은 production scheduler 재개를 검증하지 않고 provider subprocess seam 두 번 도달을 기대해 exactly-once 근거가 될 수 없다. locator publication, StateStore commit, restart reconciliation을 하나의 production-path 불변식으로 다시 닫는다.

## Archive Evidence Snapshot

- Prior plan: `agent-task/m-agent-task-runtime-target-selector/11+10_unknown_isolation/plan_local_G07_12.log`
- Prior review: `agent-task/m-agent-task-runtime-target-selector/11+10_unknown_isolation/code_review_cloud_G07_12.log`
- Verdict: `FAIL`
- Required 1: locator의 첫 durable write가 stable handoff ID보다 앞서서 atomic handoff identity 경계를 만족하지 않는다.
- Required 2: save-fault와 restart regression이 legacy helper/direct `invoke()`에 의존하고 restart에서 provider seam 2회를 기대하므로 production restart exactly-once와 sibling 불변을 검증하지 않는다.
- 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개는 scope 밖 `ReviewControlTest.test_final_channel_contract_is_top_level_and_singular` 1개만 FAIL, `py_compile` 및 `git diff --check`는 PASS했다.
- 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_G07_12.log`
- `agent-task/m-agent-task-runtime-target-selector/11+10_unknown_isolation/code_review_cloud_G07_12.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/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는 `[승인됨]`, SDD 잠금과 Milestone 구현 잠금은 `해제`다.
- S06 `context-failover`는 stable logical continuation을, S09 `dispatch-integration`은 persisted decision과 restart invocation 일치를, S11 `throughput-policy`는 normal sibling quota snapshot 불변을 요구한다.
- Evidence Map S06/S09/S11에 따라 첫 locator record identity, atomic commit save-fault rollback, StateStore reopen 뒤 provider duplicate 0회, 별도 normal sibling deep equality를 같은 구현 체크리스트와 최종 검증으로 묶는다.

### 테스트 환경 규칙

- `test_env=local`; `agent-test/local/rules.md`와 `agent-test/local/testing-smoke.md`를 읽었다.
- dispatcher unit/integration simulation은 실제 `pi`, `agy`, `claude`, `codex` process 및 네트워크를 실행하지 않는다. production `invoke()`는 통과시키되 subprocess seam은 deterministic fake/deny guard로 제어한다.
- 이 Python state transaction은 Edge-Node dev script, mock smoke, live provider preflight 범위가 아니므로 해당 profile은 적용하지 않는다.
- 검증 시작 Go identity: `/config/.local/bin/go` -> `/config/opt/go/bin/go`, `go1.26.2`, `GOROOT=/config/opt/go`; Python 검증은 Go cache를 쓰지 않는다.

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

- 현재 locator final content와 decision-context 보존은 확인하지만 첫 durable locator write의 ID 포함 여부는 확인하지 않는다.
- 현재 save-fault test는 `consume_matching_retry_handoff()`만 호출하며 `commit_retry_handoff_locator()` publication transaction을 검증하지 않는다.
- 현재 restart test는 `run_worker()`/scheduler reconciliation을 통과하지 않고 direct `invoke()` 두 번을 기대하며 normal sibling fixture가 없다.

### 심볼 참조

- `commit_retry_handoff_locator`: 정의 `dispatch.py`, `invoke()`, `run_worker()` pre-check 및 throughput tests.
- `retry_quota_refresh_context`: StateStore 초기화, retry mark/decision commit, `invoke()`, `run_worker()` 및 throughput tests.
- `retry_handoff_id`: `invoke()` locator record와 `run_worker()` pre-check에서 사용한다.
- `persisted_execution_decision`: worker/review execution과 throughput tests에서 사용한다.

### 분할 판단

- split 정책을 검토했다. first-record publication, StateStore atomic commit, scheduler restart regression은 동일한 exactly-once 불변식과 두 Python 파일을 공유한다.
- API-vs-call-site, 도메인, 독립 verification 경계가 없고 어느 부분도 단독 `complete.log`를 만들 수 없으므로 기존 단일 subtask를 유지한다.

### 범위 결정 근거

- selector policy matrix, lane/G finalizer, quota schema, Edge/Node 코드, roadmap/SDD/rules, `WORK_LOG.md`, 그리고 unrelated dirty files는 수정하지 않는다.
- `ReviewControlTest.test_final_channel_contract_is_top_level_and_singular`는 전체 suite의 기존 scope 밖 실패이므로 본 retry handoff plan에 포함하지 않는다.

### 최종 라우팅

- `evaluation_mode=isolated-reassessment`, `finalizer=finalize-task-policy.sh`, `finalizer_mode=pair`.
- build/review closure는 scope, context, verification, evidence trust, ownership, decision 모두 true다. 범위는 두 Python 파일로 닫히고 deterministic production-path regression으로 판정 가능하며 외부 권한·미결정 사항이 없다.
- build `route_basis=local-fit`, `capability_gap=none`, scores=`1/2/1/2/2`, loop-risk는 state 4개, dispatcher/StateStore 2 actor, locator/StateStore/scheduler 3 component, crash/save/restart/sibling 4 axes로 `temporal_state`, `concurrent_consistency`, `boundary_contract`, `variant_product` matched, `triggered=true`다.
- review `route_basis=official-review`, `capability_gap=none`, scores=`1/2/1/2/1`, floor=`none`.
- finalizer output: build=`PLAN-local-G08.md`, review=`CODE_REVIEW-cloud-G07.md`, official target=`codex/gpt-5.6-sol xhigh`.

## 구현 체크리스트

- [ ] retry handoff ID를 locator의 첫 durable write 전에 record에 포함하고, matching atomic commit이 false 또는 save fault이면 provider seam에 도달하지 않도록 production ordering을 고친다.
- [ ] actual scheduler/`run_worker()`를 거치는 deterministic locator publication, commit save-fault, reopen/restart 회귀로 first-record identity, provider duplicate 0회, normal sibling quota/decision/history 불변을 검증한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

### [REVIEW_REFACTOR-1] first-record handoff identity와 commit guard

- 문제: `dispatch.py:3149`가 retry context를 읽는 `:3155-3159`보다 먼저 locator를 durable write하고 `:3167`의 commit 반환값을 검사하지 않는다.
- 해결 방법:
  - retry context와 stable ID를 최초 `write_json(locator_path, record)` 전에 읽어 record에 포함한다.
  - handoff ID가 있으면 `commit_retry_handoff_locator()`이 true를 반환한 경우에만 provider process seam으로 진행한다. false 또는 save fault에서는 StateStore pre-state와 locator evidence를 보존하고 명시 오류로 종료한다.
  - Before:
    ```python
    write_json(locator_path, record)
    retry_handoff_id = retry_context.get("handoff_id")
    ```
  - After:
    ```python
    retry_handoff_id = retry_context.get("handoff_id")
    record["retry_handoff_id"] = retry_handoff_id
    write_json(locator_path, record)
    if not store.commit_retry_handoff_locator(task, retry_handoff_id, str(locator_path)):
        raise ExecutionDecisionError("retry handoff commit mismatch")
    ```
- 수정 파일 및 체크리스트:
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py`: first-write ordering, successful commit guard, failure propagation.
- 테스트 작성: 작성. 첫 write JSON snapshot과 commit mismatch/save-fault에서 subprocess guard 0회를 assert하는 focused production test를 둔다.
- 중간 검증: `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py ThroughputQuotaBatchTest.test_retry_handoff_first_locator_record_and_commit_guard -v` — PASS.

### [REVIEW_REFACTOR-2] production scheduler reopen exactly-once regression

- 문제: `test_dispatch.py:9551-9653`은 legacy helper-only save fault이고, `:9833-9844`는 restart 뒤 direct `invoke()`의 subprocess seam 2회를 정상으로 기대한다.
- 해결 방법:
  - StateStore close/reopen 전후 실제 `run_worker()` 또는 `dispatch_with_store()` 경로를 실행한다. 실제 provider 명령은 test-owned deterministic fake process로만 대체한다.
  - 최초 publication의 handoff ID, commit save-fault rollback, restart reconciliation을 같은 matrix에 넣고 committed/live locator가 있으면 second provider spawn이 없음을 assert한다.
  - retry task와 독립 normal sibling을 함께 만들고 normal sibling의 quota snapshot, decision, transition history deep equality를 확인한다.
- 수정 파일 및 체크리스트:
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py`: helper-only tests를 production-path regression matrix로 대체한다.
- 테스트 작성: 작성. `test_retry_handoff_production_save_fault_preserves_pending`과 `test_retry_restart_does_not_duplicate_provider_or_mutate_sibling`을 추가/교체한다.
- 중간 검증: `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py ThroughputQuotaBatchTest.test_retry_handoff_production_save_fault_preserves_pending ThroughputQuotaBatchTest.test_retry_restart_does_not_duplicate_provider_or_mutate_sibling -v` — PASS.

## 의존 관계 및 구현 순서

1. production first-record ordering과 commit guard를 구현한다.
2. production scheduler save-fault/reopen regression을 작성해 retry state와 normal sibling 불변을 검증한다.
3. focused, selected, 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_first_locator_record_and_commit_guard ThroughputQuotaBatchTest.test_retry_handoff_production_save_fault_preserves_pending ThroughputQuotaBatchTest.test_retry_restart_does_not_duplicate_provider_or_mutate_sibling -v` — first-write ID, save-fault rollback, reopen/restart duplicate provider 0회, sibling state 불변 PASS.
- `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 변경으로 새 실패 없음; scope 밖 `ReviewControlTest.test_final_channel_contract_is_top_level_and_singular`만 별도 후보로 기록.
- `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`의 구현 에이전트 소유 섹션을 채운다.
