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

# Plan - completing decision selected schema 엄격화

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

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

## 배경

completing decision validator는 stage와 work-unit을 확인하지만 cloud adapter의 execution class를 강제하지 않는다. 그 결과 `agy + local_model + selfcheck_required=False`가 worker completion으로 저장되고 restart에서 selfcheck로 잘못 라우팅된다. 이 follow-up은 selected 필드 타입과 adapter/class/selfcheck 조합을 strict 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_G05_2.log`, `agent-task/m-agent-task-runtime-target-selector/08+07_selfcheck_policy/code_review_cloud_G05_2.log`
- Verdict: `FAIL`
- Issue summary: Required 1, Suggested 0, Nit 0 — cloud adapter가 `local_model + selfcheck_required=False` completing decision을 valid로 받아 restart를 selfcheck로 잘못 라우팅하며 selected scalar의 `str()` coercion도 exact type 검증을 우회한다.
- 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 19건, pinned-target integration 1건, 전체 233건, `py_compile`, `git diff --check`는 PASS했다. 추가 reproducer는 `validator_accepts_cloud_adapter_local_class=True`, completion 후 `worker_done=True`, `execution_class=local_model`, restart `task_stage=selfcheck`를 확인했다.
- Roadmap carryover: Milestone Task `selfcheck-policy`, SDD Acceptance Scenario S10과 Evidence Map S10을 계속 대상으로 한다.
- Narrow reread allowed: 정확한 직전 판정 문맥이 필요할 때 위 `plan_local_G05_2.log`와 `code_review_cloud_G05_2.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`
- `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/tests/test_dispatch.py`
- `agent-task/archive/2026/07/m-agent-task-runtime-target-selector/07+06_context_review_recovery/complete.log`
- `agent-task/m-agent-task-runtime-target-selector/08+07_selfcheck_policy/PLAN-local-G05.md`의 직전 active 내용
- `agent-task/m-agent-task-runtime-target-selector/08+07_selfcheck_policy/CODE_REVIEW-cloud-G05.md`의 직전 active 내용

### SDD 기준

- SDD 경로는 `agent-roadmap/sdd/automation-runtime-bridge/agent-task-runtime-target-selector/SDD.md`, 상태는 `[승인됨]`, SDD 잠금은 해제 상태다.
- 대상은 Acceptance Scenario S10과 Evidence Map S10이며 Milestone Task id는 `selfcheck-policy`다.
- 실제 worker 완료 target이 local이면 같은 pinned target으로 selfcheck하고 cloud이면 생략한다는 기준을 selected schema의 exact type/조합 검증과 restart 회귀에 반영한다.

### 테스트 환경 규칙

- `test_env=local`; `agent-test/local/rules.md`와 `agent-test/local/testing-smoke.md`를 읽고 적용한다.
- S10은 외부 provider 실행 없이 dispatcher unit/integration simulation으로 검증한다. 대상 test class의 provider-deny guard가 실제 `invoke`와 `build_command` 호출을 금지한다.
- fresh Python `unittest`, `py_compile`, `git diff --check`를 사용한다. 원격 checkout, runtime port, secret 또는 외부 runner가 필요하지 않아 비-local preflight는 적용하지 않는다.

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

- `test_completing_decision_validation_matrix`는 valid cloud와 `cloud_model + selfcheck_required=True`만 확인하고 `agy|claude|codex + local_model + False`를 확인하지 않는다.
- selected adapter/target/execution_class가 non-string일 때 coercion 없이 거부되는 회귀가 없다.
- 모순된 cloud decision이 `_mark_worker_done()`에서 completion fields를 commit하지 않고, 이미 persisted된 restart state는 `task_stage=blocked`가 되는 결합 회귀가 없다.

### 심볼 참조

- rename/remove 대상은 없다.
- 수정 대상 `_spec_from_completing_decision()`은 `_validated_completing_decision()`과 `run_selfcheck()` 경로가 소비하며, `_completing_decision_is_valid()`, `_mark_worker_done()`, `task_stage()`가 strict validator 결과에 의존한다.

### 분할 판단

- split 정책을 먼저 평가했다. production 변경은 `dispatch.py`의 단일 completing-decision schema boundary이고 같은 test class가 모든 변형을 검증하므로 독립 배포 가능한 경계가 없다.
- 코드와 회귀 테스트 어느 한쪽만으로 완료할 수 없고 검증 profile도 같아 단일 plan이 더 안전하다.
- subtask `08+07_selfcheck_policy`의 predecessor `07+06_context_review_recovery`는 `agent-task/archive/2026/07/m-agent-task-runtime-target-selector/07+06_context_review_recovery/complete.log`로 충족됐다.

### 범위 결정 근거

- worker locator identity, stage/work-unit 검증과 task-local catch는 직전 loop에서 구현·검증됐으므로 재설계하지 않는다.
- selector route matrix, quota/failover, failure budget, official review route, 중앙 관리 `agent-ops/rules/common/**`와 `agent-ops/skills/common/**`, Milestone/SDD/contract 문서는 변경하지 않는다.
- `WORK_LOG.md`, 다른 subtask와 repo root의 기존 dirty/untracked 파일, 실제 provider 실행은 범위에서 제외한다.

### 최종 라우팅

- evaluation mode=`isolated-reassessment`; finalizer=`finalize-task-policy.sh`; finalizer mode=`pair`; missing evidence=`[]`; blocked reason=`null`.
- build closures=`scope/context/verification/evidence/ownership/decision 모두 true`; route basis=`local-fit`; capability gap=`none`; scores=`scope_coupling 1, state_concurrency 1, blast_irreversibility 1, evidence_diagnosis 1, verification_complexity 1`; final=`local-G05`; filename=`PLAN-local-G05.md`.
- build loop-risk snapshot: `ordered_transitions={state_count: 3, adverse_paths: [completion commit, restart blocked/selfcheck/review], evidence: _mark_worker_done/task_stage}`; `concurrent_consistency={actor_count: 2, constraints: [worker completion commit과 restart scheduler 관측의 persisted-state 일관성], evidence: StateStore consumer}`; `boundary_contract={component_count: 3, consumer_count: 3, constraints: [selected exact type, adapter/class/selfcheck 조합], evidence: validator/commit/stage consumer}`; `structured_interpretation={mechanisms: [nested persisted selected schema], hazards: [type coercion, class/flag contradiction], evidence: focused reproducer}`; `variant_product={independent_axis_count: 3, combination_verification_required: true, evidence: cloud adapter × execution class × selfcheck flag}`. matched signatures=`ordered_transitions, concurrent_consistency, boundary_contract, structured_interpretation, variant_product`; triggered=`true`.
- review closures=`scope/context/verification/evidence/ownership/decision 모두 true`; route basis=`official-review`; capability gap=`none`; scores=`1/1/1/1/1`; final=`cloud-G05`; filename=`CODE_REVIEW-cloud-G05.md`; execution=`codex/gpt-5.6-sol xhigh`.
- repetition/signature 기반 lane/G floor=`none`; universal cloud floor=`none`; capability-gap floor=`none`.

## 구현 체크리스트

- [x] selected adapter/target/execution_class를 coercion 없는 non-empty string으로 검증하고 Pi는 `local_model + True`, cloud adapter는 `cloud_model + False` 조합만 허용한다.
- [x] 모든 cloud adapter의 `local_model + False`, non-string selected field, valid local/cloud 조합을 provider-deny validation/commit/restart 회귀로 고정한다.
- [x] focused class와 pinned-target integration, 전체 Python suite, `py_compile`, `git diff --check`를 fresh process에서 통과시킨다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

## 구현 항목

### [REVIEW_REFACTOR-1] Strict selected schema와 cloud class 불변식

- 문제: `dispatch.py:1179-1182`는 selected scalar를 `str()`로 강제 변환하고, `dispatch.py:1211-1220`의 cloud branch는 `selfcheck_required=False`만 확인한다. 따라서 `agy + local_model + False`가 valid completion으로 저장되어 `task_stage()`가 selfcheck를 선택한다.
- 해결 방법: selected의 adapter/target/execution_class를 원본 타입 그대로 읽어 각각 non-empty string인지 먼저 검증한다. Pi는 `iop/` target, `local_model`, `selfcheck_required is True`를 모두 요구하고, `agy|claude|codex`는 `cloud_model`, `selfcheck_required is False`를 모두 요구한다. 기존 `_validated_completing_decision()` 공유 경계를 유지해 live completion과 restart가 같은 validator를 소비하게 한다.

Before (`dispatch.py:1179-1220`):

```python
adapter = str(selected.get("adapter") or "")
target = str(selected.get("target") or "")
execution_class = str(selected.get("execution_class") or "")
...
if adapter not in {"agy", "claude", "codex"}:
    raise ExecutionDecisionError(...)
if selfcheck_required:
    raise ExecutionDecisionError(...)
return AgentSpec(adapter, target, display, local_pi=False)
```

After:

```python
adapter = selected.get("adapter")
target = selected.get("target")
execution_class = selected.get("execution_class")
if not all(isinstance(value, str) and value for value in (adapter, target, execution_class)):
    raise ExecutionDecisionError(...)
...
if adapter in {"agy", "claude", "codex"}:
    if execution_class != "cloud_model" or selfcheck_required is not False:
        raise ExecutionDecisionError(...)
    return AgentSpec(adapter, target, f"{adapter}/{target}", local_pi=False)
```

- 수정 파일 및 체크리스트:
  - [x] `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py`: exact selected type와 adapter/class/selfcheck 조합을 strict validator에 반영한다.
  - [x] `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py`: cloud adapter/class product와 non-string selected field를 validation, `_mark_worker_done()`, restart assertions에 추가한다.
- 테스트 작성: 작성한다. `CompletingTargetSelfcheckTest.test_completing_decision_validation_matrix`를 subtest matrix로 확장해 `agy`, `claude`, `codex` 각각의 `local_model + False`가 invalid인지 확인하고, adapter/target/execution_class non-string 변형을 거부한다. direct completion test는 invalid decision에서 `worker_done`이 commit되지 않음을, restart test는 malformed completed state가 `blocked`이고 selfcheck/provider command가 호출되지 않음을 assertion한다.
- 중간 검증: `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py CompletingTargetSelfcheckTest -v` — strict schema와 completion/restart 회귀 전체 PASS.

## 의존 관계 및 구현 순서

1. `_spec_from_completing_decision()`의 exact type/조합 검증을 먼저 고정한다.
2. 같은 validator를 사용하는 validation, completion, restart 회귀를 추가한다.
3. 전체 검증 후 review stub의 구현 에이전트 소유 섹션만 채운다.

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `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 |

## 최종 검증

1. `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py CompletingTargetSelfcheckTest -v` — strict completion validation과 task-local fail-closed 회귀 전체 PASS.
2. `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py SelectorDispatcherIntegrationTest.test_completing_target_controls_selfcheck_and_reuses_pin -v` — valid local/cloud completing target integration PASS.
3. `python3 -m unittest discover -s agent-ops/skills/project/orchestrate-agent-task-loop/tests -p 'test_*.py'` — 전체 dispatcher suite PASS; cached output은 사용하지 않고 fresh Python process 결과를 기록한다.
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` — syntax PASS.
5. `git diff --check` — whitespace error 없음.

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