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

# Code Review Reference - REVIEW_REFACTOR

> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> The task is NOT complete until every implementation-owned section below is filled in.
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
> If implementation is blocked, record the exact blocker, attempted commands/output, and resume condition only in implementation-owned evidence fields.
> Do not ask the user directly, present choices, call user-input tools, create control-plane stop files, or classify the next state.
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume.
> Follow the ownership table at the bottom of this file for which sections you own.

## 개요

date=2026-07-26
task=m-agent-task-runtime-target-selector/08+07_selfcheck_policy, plan=1, tag=REVIEW_REFACTOR

## 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`만 읽는다.

## 이 파일을 읽는 리뷰 에이전트에게

> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.

각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.

1. 판정을 append한다.
2. `CODE_REVIEW-cloud-G05.md` → `code_review_cloud_G05_1.log`, `PLAN-local-G05.md` → `plan_local_G05_1.log`로 아카이브한다.
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-agent-task-runtime-target-selector/08+07_selfcheck_policy/`로 이동한다. WARN/FAIL이면 code-review skill이 요구하는 다음 filesystem state를 완전히 작성한다.
4. PASS이고 task group이 `m-agent-task-runtime-target-selector`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다.
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.

---

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| REVIEW_REFACTOR-1 Completing decision identity와 scheduler fail-closed | [x] |
| REVIEW_REFACTOR-2 Canonical Pi runtime spec과 격리 테스트 | [x] |

## 구현 체크리스트

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

## 코드리뷰 전용 체크리스트

> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.

- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [x] active `CODE_REVIEW-cloud-G05.md`를 `code_review_cloud_G05_1.log`로 아카이브한다.
- [x] active `PLAN-local-G05.md`를 `plan_local_G05_1.log`로 아카이브한다.
- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다.
- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [ ] PASS이면 active task 디렉터리 `agent-task/m-agent-task-runtime-target-selector/08+07_selfcheck_policy/`를 `agent-task/archive/YYYY/MM/m-agent-task-runtime-target-selector/08+07_selfcheck_policy/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [ ] PASS이고 task group이 `m-agent-task-runtime-target-selector`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-agent-task-runtime-target-selector/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [x] WARN/FAIL이면 code-review skill의 판정에 맞는 다음 filesystem state를 작성하고 `complete.log`를 작성하지 않는다.

## 계획 대비 변경 사항

1. `test_completing_target_controls_selfcheck_and_reuses_pin`은 원래 `DispatcherCanonicalFailoverIntegrationTest`에 정의되어 있었으나, PLAN에서 참조하는 `SelectorDispatcherIntegrationTest.test_completing_target_controls_selfcheck_and_reuses_pin`과 일치하도록 클래스를 이동했다.
2. 기존 테스트 4건(`test_worker_persists_the_actual_promoted_target`, `test_exhausted_selfcheck_budget_does_not_invoke_model`, `test_selfcheck_incomplete_tenth_pass_blocks_task`, `test_review_preflight_failure_still_drains_independent_worker`)이 새 validating `_mark_worker_done`과 `_spec_from_completing_decision`과 호환되지 않아 보정했다:
   - `test_worker_persists_the_actual_promoted_target`: completing decision adapter를 `agy`→`codex`, target을 `Gemini 3.6 Flash (High)`→`gpt-5.6-terra`로 worker locator와 일치시킴.
   - `test_exhausted_selfcheck_budget_does_not_invoke_model`: Pi target을 `ornith:35b`→`iop/ornith:35b`로 `iop/` prefix 추가.
   - `test_selfcheck_incomplete_tenth_pass_blocks_task`:同上, Pi target을 `ornith:35b`→`iop/ornith:35b`.
   - `test_review_preflight_failure_still_drains_independent_worker`: `worker_done=True`에 valid completing decision과 execution_class를 추가하여 scheduler blocked 전환 방지.
3. 검증 명령은 PLAN의 최종 검증 5단계와 일치한다.

## 주요 설계 결정

1. **_mark_worker_done() identity 검증**: worker completion 시 persisted/fallback decision의 adapter/target을 worker CLI/model과 정규화하여 대조한다. Pi target의 경우 adapter=`pi`, target=`iop/` prefix 필수, model=`target.removeprefix('iop/')`와 일치해야 한다. Cloud target의 경우 adapter=worker_cli, target=worker_model와 일치해야 한다. mismatch 시 `ExecutionDecisionError`를 raising하여 `worker_done` 기록을 차단한다.
2. **_spec_from_completing_decision() 정규화**: Pi completing decision의 `target`에서 `iop/` prefix를 제거하여 runtime `AgentSpec.model`에 저장한다. display/canonical identity는 `pi/{target}` 규약으로 보존한다. Pi target은 `execution_class=local_model`, `selfcheck_required=True`를 요구한다. Cloud adapter는 `agy/claude/codex`만 허용하고 `selfcheck_required=True`를 거부한다.
3. **task_stage() fail-closed**: `worker_done=True`인데 completing decision이 없거나 손상된 경우 `task_stage()`가 `review`가 아닌 `blocked`를 반환한다. valid local_model decision은 `selfcheck_done`이 False일 때 `selfcheck`를 반환한다.
4. **completing_decision_requires_selfcheck() 단순화**: `execution_class == 'local_model'`일 때만 True를 반환한다. cloud_model은 selfcheck 없이 바로 review로 진행한다.
5. **selfcheck 격리**: `run_selfcheck()`는 `_spec_from_completing_decision()`으로 spec을 추출하고 selector/quota probe를 호출하지 않는다. missing/malformed completing decision은 `blocked`로 처리한다.

## 리뷰어를 위한 체크포인트

- canonical `iop/` target이 runtime model과 display/canonical identity로 각각 올바르게 변환되는가.
- 성공 locator와 persisted completing decision이 다르면 `worker_done` 없이 task-local blocker로 차단되는가.
- `worker_done=true`의 missing/malformed completing decision이 restart에서 review로 진행하지 않는가.
- selfcheck가 selector/quota를 호출하지 않고 test class의 provider-deny guard가 실제 외부 호출을 차단하는가.

## 검증 결과

### CompletingTargetSelfcheckTest

명령: `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py CompletingTargetSelfcheckTest -v`

```
test_canonical_model_command_generation (__main__.CompletingTargetSelfcheckTest.test_canonical_model_command_generation)
Selfcheck spec.model is normalized (iop/ prefix stripped) for command generation. ... ok
test_cloud_completing_decision_skips_selfcheck (__main__.CompletingTargetSelfcheckTest.test_cloud_completing_decision_skips_selfcheck)
execution_class=cloud_model skips selfcheck entirely. ... ok
test_completing_decision_requires_selfcheck_matrix (__main__.CompletingTargetSelfcheckTest.test_completing_decision_requires_selfcheck_matrix)
Matrix: local_model→True, cloud_model→False, missing→False. ... ok
test_completing_decision_validation_matrix (__main__.CompletingTargetSelfcheckTest.test_completing_decision_validation_matrix)
_completing_decision_is_valid rejects missing/malformed decisions. ... ok
test_identity_mismatch_decision_blocked_at_scheduler (__main__.CompletingTargetSelfcheckTest.test_identity_mismatch_decision_blocked_at_scheduler)
worker_done=True with missing completing decision blocks at scheduler entry. ... ok
test_identity_mismatch_fails_closed (__main__.CompletingTargetSelfcheckTest.test_identity_mismatch_fails_closed)
Missing or malformed completing decision blocks selfcheck. ... ------------------------------------------
작업차단: completing_target_test
------------------------------------------
reason=missing-completing-decision
ok
test_identity_mismatch_malformed_decision_blocked (__main__.CompletingTargetSelfcheckTest.test_identity_mismatch_malformed_decision_blocked)
worker_done=True with malformed completing decision blocks at scheduler. ... ok
test_local_completing_decision_triggers_selfcheck (__main__.CompletingTargetSelfcheckTest.test_local_completing_decision_triggers_selfcheck)
execution_class=local_model schedules exactly one selfcheck. ... ------------------------------------------
자가검증시작: completing_target_test
------------------------------------------
model=pi/iop/laguna-s:2.1
plan=/tmp/tmpq0lfny1b/agent-task/completing_target_test/PLAN-local-G08.md
work_log=/tmp/tmpq0lfny1b/agent-task/completing_target_test/WORK_LOG.md
ok
test_mark_worker_done_validates_cloud_identity (__main__.CompletingTargetSelfcheckTest.test_mark_worker_done_validates_cloud_identity)
_mark_worker_done rejects cloud decision with mismatched worker CLI. ... ok
test_mark_worker_done_validates_pi_identity (__main__.CompletingTargetSelfcheckTest.test_mark_worker_done_validates_pi_identity)
_mark_worker_done rejects Pi decision with mismatched worker model. ... ok
test_restart_does_not_duplicate_selfcheck (__main__.CompletingTargetSelfcheckTest.test_restart_does_not_duplicate_selfcheck)
After restart, already-completed selfcheck is not re-executed. ... ok
test_selector_probe_not_invoked_during_selfcheck (__main__.CompletingTargetSelfcheckTest.test_selector_probe_not_invoked_during_selfcheck)
Selfcheck does not call selector or quota probe. ... ------------------------------------------
자가검증시작: completing_target_test
------------------------------------------
model=pi/iop/laguna-s:2.1
plan=/tmp/tmp5g10eh7l/agent-task/completing_target_test/PLAN-local-G08.md
work_log=/tmp/tmp5g10eh7l/agent-task/completing_target_test/WORK_LOG.md
ok
test_selfcheck_reuses_completing_target_no_selector_call (__main__.CompletingTargetSelfcheckTest.test_selfcheck_reuses_completing_target_no_selector_call)
Selfcheck uses the completing decision's target, not re-evaluating selector. ... ------------------------------------------
자가검증시작: completing_target_test
------------------------------------------
model=pi/iop/laguna-s:2.1
plan=/tmp/tmpdbvdmz4l/agent-task/completing_target_test/PLAN-local-G08.md
work_log=/tmp/tmpdbvdmz4l/agent-task/completing_target_test/WORK_LOG.md
ok
test_spec_from_completing_decision_normalizes_pi_target (__main__.CompletingTargetSelfcheckTest.test_spec_from_completing_decision_normalizes_pi_target)
_spec_from_completing_decision strips iop/ prefix from model. ... ok
test_spec_from_completing_decision_rejects_invalid_pi_target (__main__.CompletingTargetSelfcheckTest.test_spec_from_completing_decision_rejects_invalid_pi_target)
_spec_from_completing_decision rejects Pi target without iop/ prefix. ... ok
test_spec_from_completing_decision_rejects_non_pi_cloud (__main__.CompletingTargetSelfcheckTest.test_spec_from_completing_decision_rejects_non_pi_cloud)
_spec_from_completing_decision rejects cloud target with selfcheck_required=True. ... ok
test_worker_persists_actual_completing_decision_and_execution_class (__main__.CompletingTargetSelfcheckTest.test_worker_persists_actual_completing_decision_and_execution_class)
Worker success records the completing decision, not the initial one. ... ------------------------------------------
작업시작: completing_target_test
------------------------------------------
model=pi/iop/laguna-s:2.1
plan=/tmp/tmpxmz0y5hb/agent-task/completing_target_test/PLAN-local-G08.md
work_log=/tmp/tmpxmz0y5hb/agent-task/completing_target_test/WORK_LOG.md
ok

----------------------------------------------------------------------
Ran 17 tests in 0.053s

OK
```

Exit code: 0

### pinned target integration

명령: `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py SelectorDispatcherIntegrationTest.test_completing_target_controls_selfcheck_and_reuses_pin -v`

```
test_completing_target_controls_selfcheck_and_reuses_pin (__main__.SelectorDispatcherIntegrationTest.test_completing_target_controls_selfcheck_and_reuses_pin) ... ------------------------------------------
작업시작: 01_unit
------------------------------------------
task=selector_dispatch_integration/01_unit
model=agy/Gemini 3.6 Flash (Medium)
plan=/tmp/tmphvmoz7x_/agent-task/selector_dispatch_integration/01_unit/PLAN-local-G08.md
work_log=/tmp/tmphvmoz7x_/agent-task/selector_dispatch_integration/WORK_LOG.md
------------------------------------------
모델승격: 01_unit
------------------------------------------
task=selector_dispatch_integration/01_unit
from=agy/Gemini 3.6 Flash (Medium)
to=pi/iop/laguna-s:2.1
failure_class=provider-quota
failure_source=unverified
provider_transport_failure_confirmed=false
locator=/tmp/tmphvmoz7x_/attempt-agy/locator.json
------------------------------------------
자가검증시작: 01_unit
------------------------------------------
task=selector_dispatch_integration/01_unit
model=pi/iop/laguna-s:2.1
plan=/tmp/tmphvmoz7x_/agent-task/selector_dispatch_integration/01_unit/PLAN-local-G08.md
work_log=/tmp/tmphvmoz7x_/agent-task/selector_dispatch_integration/WORK_LOG.md
------------------------------------------
작업시작: 01_unit
------------------------------------------
task=selector_dispatch_integration/01_unit
model=pi/iop/laguna-s:2.1
plan=/tmp/tmp41x291e_/agent-task/selector_dispatch_integration/01_unit/PLAN-local-G08.md
work_log=/tmp/tmp41x291e_/agent-task/selector_dispatch_integration/WORK_LOG.md
------------------------------------------
모델승격: 01_unit
------------------------------------------
task=selector_dispatch_integration/01_unit
from=pi/iop/laguna-s:2.1
to=agy/Gemini 3.6 Flash (Medium)
failure_class=provider-stream-disconnect
failure_source=unverified
provider_transport_failure_confirmed=false
locator=/tmp/tmp41x291e_/attempt-pi/locator.json
------------------------------------------
작업시작: 01_unit
------------------------------------------
task=selector_dispatch_integration/01_unit
model=claude/claude-opus-4-8 xhigh
plan=/tmp/tmpc__84due/agent-task/selector_dispatch_integration/01_unit/PLAN-cloud-G07.md
work_log=/tmp/tmpc__84due/agent-task/selector_dispatch_integration/WORK_LOG.md
ok

----------------------------------------------------------------------
Ran 1 test in 0.099s

OK
```

Exit code: 0

### 전체 Python suite

명령: `python3 -m unittest discover -s agent-ops/skills/project/orchestrate-agent-task-loop/tests -p 'test_*.py'`

```
----------------------------------------------------------------------
Ran 231 tests in 14.585s

OK
```

Exit code: 0

### py_compile

명령: `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 code: 0

### diff check

명령: `git diff --check`

```
(출력 없음)
```

Exit code: 0

---

> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section?**
> If anything is blank, go back and fill it in before saving this file.
> Leave review-agent-only sections unchanged.

## 섹션 소유권

| Section | Owner | Note |
|---------|-------|------|
| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute these (archive, complete.log, and task-directory archive move are review-agent only) |
| Roadmap Targets | Fixed at stub creation from plan when present | Implementing agent must not modify; code-review copies it into `complete.log` as `Roadmap Completion` only on PASS |
| Archive Evidence Snapshot | Fixed at stub creation from plan when present | Implementing agent uses it as default prior-loop context; read only the specific archive files cited there when more detail is required |
| Agent UI Completion | Mixed | Present only for plan-required agent-ui code work; implementing agent fills actual evidence, review agent applies `구현됨` status/evidence update on PASS and copies the section into `complete.log` |
| 구현 항목별 완료 여부 (item names) | Fixed at stub creation | Implementing agent checks `[ ]` → `[x]` only |
| 구현 체크리스트 (item text/order) | Fixed at stub creation from plan | Implementing agent checks `[ ]` → `[x]` only |
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify or check this section |
| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholder text with actual content |
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Pre-filled from plan |
| 검증 결과 (section headings + commands) | Fixed at stub creation | Implementing agent fills in command output only; command changes require a `계획 대비 변경 사항` entry |
| 코드리뷰 결과 | Review agent appends | Not included in stub |

## 코드리뷰 결과

- 종합 판정: FAIL

### 차원별 평가

| 차원 | 평가 | 근거 |
|------|------|------|
| Correctness | Fail | completing decision의 stage/work-unit 및 adapter/class/selfcheck 일관성이 검증되지 않고 locator mismatch가 task-local blocker가 아닌 coroutine 예외로 전파된다. |
| Completeness | Fail | 계획이 요구한 worker completion identity/schema 검증과 원자적 task-local fail-closed 경계가 완성되지 않았다. |
| Test coverage | Fail | 231개 테스트는 통과하지만 잘못된 stage/work-unit, 모순된 adapter/class/selfcheck 조합, `run_worker` mismatch blocker를 고정하지 않는다. |
| API contract | Pass | 외부 API와 agent-contract 변경은 없으며 dispatcher 내부 persisted-state 계약 범위다. |
| Code quality | Pass | 대상 변경에서 디버그 출력, 잔여 TODO, 죽은 코드는 발견되지 않았다. |
| Implementation deviation | Fail | 계획의 “worker stage/work-unit 검증”과 “mismatch는 task-local blocker” 요구가 구현 및 테스트에서 누락됐다. |
| Verification trust | Fail | 계획 명령은 모두 PASS했지만 최소 재현이 테스트가 다루지 않는 completion-state 위반을 확인했다. |
| Spec conformance | Fail | SDD S10의 completing target 기반 완료 경계가 malformed persisted state와 mismatch에서 fail closed 되지 않는다. |

### 발견된 문제

- Required — `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py:4375`: `_mark_worker_done()`은 `selected.adapter/target` 일부만 locator와 비교하고 decision의 `stage=worker`, 현재 `work_unit_id`, `selfcheck_required`, adapter와 `execution_class`의 일관성을 검증하지 않는다. 실제로 `stage=review`, 다른 work-unit의 Pi decision도 `worker_done=True`로 저장된다. 현재 task/plan/tag에서 파생한 worker identity와 완전한 selected schema를 하나의 completion validator로 검증하고, persisted worker decision이 존재하지만 malformed이면 initial decision으로 폴백하지 말고 fail closed 하라.
- Required — `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py:1573`: `_completing_decision_is_valid()`은 adapter/target 비어 있음과 execution class enum만 확인해 `pi + cloud_model + selfcheck_required=False`, `agy + local_model`, boolean 누락을 valid로 본다. 첫 조합은 `task_stage()`에서 곧바로 `review`로 진행하고 두 번째는 잘못된 `selfcheck`로 들어간다. `_mark_worker_done()`과 restart scheduling이 같은 strict validator를 사용해 valid Pi local/required 및 cloud nonlocal/not-required decision만 selfcheck/review로 진행하게 하라.
- Required — `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py:4344`: `_mark_worker_done()`의 `ExecutionDecisionError`를 `run_worker()`가 잡지 않아 locator mismatch가 `blocked=None`, `worker_done=False`인 채 agent coroutine exception으로 빠진다. 예외를 task-local blocker와 `작업차단` evidence로 원자적으로 기록하고, `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py:962`의 matrix와 direct-helper 테스트를 실제 `run_worker()` mismatch, wrong stage/work-unit, 모순된 adapter/class/selfcheck restart state까지 확장하라.

### 검증 재실행

- `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py CompletingTargetSelfcheckTest -v` — PASS, 17건.
- `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py SelectorDispatcherIntegrationTest.test_completing_target_controls_selfcheck_and_reuses_pin -v` — PASS, 1건.
- `python3 -m unittest discover -s agent-ops/skills/project/orchestrate-agent-task-loop/tests -p 'test_*.py'` — PASS, 231건.
- `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` — PASS.
- `git diff --check` — PASS.
- focused reproducer — `pi_cloud_false: valid=True stage=review`, `agy_local_false: valid=True stage=selfcheck`, `cloud_missing_bool: valid=True stage=review`, `run_worker_exception=ExecutionDecisionError`, `worker_done=False blocked=None`, wrong `stage=review/work_unit_id=other::plan-99::tag-WRONG`가 `worker_done=True`로 저장됨을 확인했다.

### 다음 단계

- FAIL: 위 Required 3건을 같은 task path의 follow-up PLAN에서 strict completion validation과 task-local blocker 회귀로 수정하고 fresh routing된 review pair로 다시 검토한다.
