<!-- task=m-agent-task-runtime-target-selector/08+07_selfcheck_policy plan=1 tag=REVIEW_REFACTOR -->

# Plan - completing target selfcheck fail-closed 보정

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

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

## 배경

첫 review에서 canonical Pi target이 runtime model에 그대로 남고, 성공 locator와 completing decision의 identity가 검증되지 않으며, missing/malformed completion state가 review로 통과하는 결함이 확인됐다. 테스트도 실제 mismatch와 provider 격리를 고정하지 못했다. 이 follow-up은 SDD S10을 충족하도록 completion boundary를 fail closed로 보정한다.

## 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:
  - `selfcheck-policy`: 실제 worker 완료 target의 local/cloud 성격으로 selfcheck 결정
- Completion mode: check-on-pass

## Archive Evidence Snapshot

- Task: `agent-task/m-agent-task-runtime-target-selector/08+07_selfcheck_policy/`
- Prior loop artifacts: `agent-task/m-agent-task-runtime-target-selector/08+07_selfcheck_policy/plan_local_G04_0.log`, `agent-task/m-agent-task-runtime-target-selector/08+07_selfcheck_policy/code_review_cloud_G05_0.log`
- Verdict: `FAIL`
- Issue summary: Required 3, Suggested 0, Nit 0 — canonical Pi target prefix가 runtime model에 남음; locator/completing decision identity와 malformed scheduler state가 fail closed 되지 않음; 새 test class의 provider-deny guard와 실제 mismatch assertion이 없음.
- 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: 집중 7건과 전체 221건, `py_compile`, `git diff --check`는 PASS했지만 focused reproducer가 `selected_target=iop/laguna-s:2.1`, `worker_model=laguna-s:2.1`, `selfcheck_model=iop/laguna-s:2.1`, `missing_decision_stage=review`를 재현했다.
- Roadmap carryover: Milestone Task `selfcheck-policy`, SDD Acceptance Scenario S10과 Evidence Map S10을 계속 대상으로 한다.
- Narrow reread allowed: 정확한 이전 판정 문맥이 필요할 때 위 `plan_local_G04_0.log`와 `code_review_cloud_G05_0.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-ops/skills/project/orchestrate-agent-task-loop/SKILL.md`
- `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/scripts/execution_target_policy.py`
- `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py`
- `agent-task/m-agent-task-runtime-target-selector/08+07_selfcheck_policy/plan_local_G04_0.log`
- `agent-task/m-agent-task-runtime-target-selector/08+07_selfcheck_policy/code_review_cloud_G05_0.log`
- `agent-task/archive/2026/07/m-agent-task-runtime-target-selector/07+06_context_review_recovery/complete.log`

### SDD 기준

- SDD 상태는 `[승인됨]`이며 잠금 해제됐다.
- 대상 Acceptance Scenario와 Evidence Map은 S10 `selfcheck-policy`다.
- Gemini→Laguna 완료 selfcheck, Laguna→Gemini/cloud 완료 미실행, pinned local target 재사용을 completion boundary와 집중 테스트에 반영한다.

### 테스트 환경 규칙

- `test_env=local`; `agent-test/local/rules.md`와 `testing-smoke.md`를 확인했다.
- Edge-Node/full-cycle smoke 명령은 외부 provider를 실제 호출하는 이 범위에 적용하지 않는다. provider-deny guard를 둔 Python dispatcher unit/integration 검증을 사용한다.
- 현재 checkout의 Python `unittest` suite와 `py_compile`, `git diff --check`를 fresh process로 실행한다.

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

- selector의 canonical `iop/laguna-s:2.1` target이 selfcheck runtime model로 정규화되는 assertion이 없다.
- 실제 failover completing decision과 성공 locator의 일치·불일치가 scheduler 경계에서 검증되지 않는다.
- missing/malformed completing decision을 가진 restart state가 review로 통과한다.
- `CompletingTargetSelfcheckTest`에 기본 provider-deny guard가 없다.

### 심볼 참조

- `execution_target_policy.py:45`: canonical Laguna target은 `iop/laguna-s:2.1`이다.
- `dispatch.py:1166-1187`: `_spec_from_completing_decision()`이 target을 직접 `AgentSpec.model`에 넣는다.
- `dispatch.py:1530-1537`: `completing_decision_requires_selfcheck()`이 invalid state를 no-selfcheck로 축약한다.
- `dispatch.py:1618-1621`: `task_stage()`가 invalid completion state를 review로 보낸다.
- `dispatch.py:2615-2618`: `build_command()`는 provider와 model을 별도 인자로 만든다.
- `dispatch.py:4294-4337`: `_mark_worker_done()`이 decision identity와 locator를 대조하지 않는다.
- `test_dispatch.py:388`: `CompletingTargetSelfcheckTest`에 provider-deny guard가 없다.

### 분할 판단

- completion state validation, scheduler fail-closed, runtime spec normalization은 하나의 completing-decision 계약을 공유하므로 같은 task에 유지한다.
- predecessor `07+06_context_review_recovery`는 archived `complete.log`의 PASS로 충족됐다.

### 범위 제외

- selector route matrix, failover eligibility, quota/admission, failure budget, official review route는 변경하지 않는다.
- 중앙 관리 common rules/skills 문서는 수정하지 않는다.
- 새 테스트 framework나 provider 실제 호출을 추가하지 않는다.

### 최종 라우팅

- 이전 결과를 neutral input에서 제외한 `isolated-reassessment`로 라우팅했다.
- build/review closure는 모두 `true`, capability gap은 `none`이다.
- build route basis=`local-fit`; scores=`1/1/1/1/1`, final=`local-G05`, filename=`PLAN-local-G05.md`.
- review route basis=`official-review`; scores=`1/1/1/1/1`, final=`cloud-G05`, filename=`CODE_REVIEW-cloud-G05.md`; official execution=`codex/gpt-5.6-sol xhigh`.
- loop-risk signatures=`temporal_state,boundary_contract,structured_interpretation,variant_product`; 감사 결과는 lane/G를 바꾸지 않는다.

## 구현 체크리스트

- [ ] worker 성공 시 completing decision의 worker stage/work-unit과 normalized adapter/target이 성공 locator와 일치할 때만 completion state를 원자적으로 저장하고 mismatch는 task-local blocker로 차단한다.
- [ ] `worker_done=true`의 missing/malformed completing decision이 selfcheck 없이 review로 진행하지 않으며 valid local/cloud decision만 selfcheck/review로 결정된다.
- [ ] pinned local Pi completing decision은 selector/quota 재평가 없이 canonical `iop/` target을 runtime model로 정확히 정규화해 같은 Laguna target으로 selfcheck한다.
- [ ] `CompletingTargetSelfcheckTest`에 provider-deny guard를 설치하고 actual failover, canonical model/command, identity mismatch, malformed restart, selector/probe 미호출을 검증한다.
- [ ] focused class/method, 전체 Python suite, `py_compile`, `git diff --check`를 fresh checkout state에서 통과시킨다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

## 구현 항목

### [REVIEW_REFACTOR-1] Completing decision identity와 scheduler fail-closed

- 문제: `_mark_worker_done()`이 persisted/fallback decision을 성공 locator와 대조하지 않고, scheduler는 missing/malformed completing decision을 no-selfcheck로 해석해 review로 진행한다.
- 해결 방법:
  - worker completion schema에서 decision stage/work-unit, adapter, target, execution_class, selfcheck_required를 검증한다.
  - canonical decision identity를 locator의 `worker_cli`/`worker_model`과 같은 runtime identity로 정규화해 비교한다.
  - mismatch/invalid state는 `worker_done`을 기록하지 않고 task-local blocker로 fail closed 한다.
  - restart에서 `worker_done=true`인데 completing decision이 없거나 손상된 경우 review로 진행하지 않는다.
- 수정 파일 및 체크리스트:
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py`: completion validator, `_mark_worker_done`, `task_stage` 경계.
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py`: actual failover, mismatch, malformed restart 회귀.
- 중간 검증: focused class에서 locator/decision 일치 성공, mismatch/invalid state 차단을 확인한다.

### [REVIEW_REFACTOR-2] Canonical Pi runtime spec과 격리 테스트

- 문제: `_spec_from_completing_decision()`이 canonical Pi target의 `iop/` prefix를 runtime model에 남기고, 새 test class는 provider 호출 deny guard와 생성 명령 assertion이 없다.
- 해결 방법:
  - persisted decision은 `adapter=pi`, `execution_class=local_model`, `selfcheck_required is True`, canonical target `iop/` prefix를 요구한다.
  - runtime `AgentSpec.model`은 `iop/` prefix를 제거하고 display/canonical identity는 `pi/{target}` 규약을 보존한다.
  - selfcheck는 selector initial/failover와 quota probe를 호출하지 않고 pinned decision만 복원한다.
  - class-level provider-deny guard를 설치하고 selfcheck spec 및 `build_command`의 `--provider iop --model laguna-s:2.1`을 assertion한다.
- 수정 파일 및 체크리스트:
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py`: `_spec_from_completing_decision` validation/normalization.
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py`: provider deny, canonical command, selector/probe 미호출.
- 중간 검증: focused class와 pinned-target integration method가 PASS해야 한다.

## 의존 관계 및 구현 순서

1. predecessor `07+06_context_review_recovery`의 archived `complete.log` PASS는 충족됐다.
2. REVIEW_REFACTOR-1의 authoritative completion state를 REVIEW_REFACTOR-2가 소비한다.
3. 두 항목과 검증을 완료한 뒤 review stub만 채우고 active pair를 유지한다.

## 수정 파일 요약

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

## 최종 검증

1. `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py CompletingTargetSelfcheckTest -v`
2. `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py SelectorDispatcherIntegrationTest.test_completing_target_controls_selfcheck_and_reuses_pin -v`
3. `python3 -m unittest discover -s agent-ops/skills/project/orchestrate-agent-task-loop/tests -p 'test_*.py'`
4. `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`
5. `git diff --check`

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