<!-- task=m-agent-task-runtime-target-selector/01_selector_contract plan=2 tag=REVIEW_API -->

# Selector prior decision 전체 중첩 schema 검증

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

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

## 배경

두 번째 구현은 prior decision과 quota snapshot의 대표적인 잘못된 container를 명시 오류로 바꿨지만, resume에서 재사용하는 `candidates`, `decision`, `quota`의 하위 schema는 아직 검증하지 않는다. 유효한 container 안의 누락 field나 잘못된 enum이 성공 출력으로 그대로 전파되므로, initial 출력 계약을 기준으로 남은 중첩 schema를 닫는다.

## Archive Evidence Snapshot

- Prior task: `agent-task/m-agent-task-runtime-target-selector/01_selector_contract`
- Archived plan: `agent-task/m-agent-task-runtime-target-selector/01_selector_contract/plan_cloud_G07_1.log`
- Archived review: `agent-task/m-agent-task-runtime-target-selector/01_selector_contract/code_review_cloud_G09_1.log`
- Verdict: `FAIL`
- Findings: Required 1, Suggested 0, Nit 0
- Required: `prior_decision.candidates`, `decision`, `quota`가 container 검사만 통과하면 불완전한 field/type/enum을 resume 성공 출력으로 재방출한다.
- Affected files: `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/select_execution_target.py`, `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_select_execution_target.py`
- Verification evidence: selector/policy 통합 25개와 대상 파일 `py_compile`은 PASS했다. 집중 재현은 `candidates=[{}]`, `decision={}`, `quota={"mode":"bogus"}`가 모두 성공하는 것을 확인했다.
- Roadmap carryover: `selector-contract`, `time-route`, `grade-route`; SDD S01의 완전한 selector JSON이 현재 차단 항목이다.
- 필요한 경우 위 두 archived log만 prior-loop 상세 근거로 읽는다. `agent-task/archive/**`를 탐색하지 않는다.

## 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:
  - `selector-contract`: task file 기반 selector JSON 계약
  - `time-route`: KST 주야간 경계 route
  - `grade-route`: worker/review G01~G10 route matrix
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-ops/rules/common/philosophy.md`
- `agent-ops/rules/common/rules-agent-spec.md`
- `agent-ops/skills/common/router.md`
- `agent-ops/skills/common/code-review/SKILL.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-ops/skills/common/finalize-task-routing/SKILL.md`
- `agent-ops/skills/common/plan/templates/review-stub-template.md`
- `agent-roadmap/current.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-spec/index.md`
- `agent-contract/index.md`
- `agent-test/local/rules.md`
- `agent-ops/skills/project/orchestrate-agent-task-loop/SKILL.md`
- `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_select_execution_target.py`
- `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_execution_target_policy.py`
- `agent-task/m-agent-task-runtime-target-selector/01_selector_contract/plan_cloud_G07_0.log`
- `agent-task/m-agent-task-runtime-target-selector/01_selector_contract/code_review_cloud_G09_0.log`
- `agent-task/m-agent-task-runtime-target-selector/01_selector_contract/plan_cloud_G07_1.log`
- `agent-task/m-agent-task-runtime-target-selector/01_selector_contract/code_review_cloud_G09_1.log`

### SDD 기준

- SDD: `agent-roadmap/sdd/automation-runtime-bridge/agent-task-runtime-target-selector/SDD.md`
- 상태: `[승인됨]`, SDD 잠금 `해제`, 사용자 리뷰 없음
- 직접 대상: S01→`selector-contract`
- 회귀 대상: S02→`time-route`, S03→`grade-route`
- Evidence Map: S01은 완전한 selector schema와 invalid-input 단위 테스트를 요구하고, S02/S03은 KST 네 경계 및 worker/review 전체 matrix 회귀를 요구한다.
- 구현 체크리스트는 S01의 남은 candidate/decision/quota 중첩 계약을 완성하고, 최종 검증에서 S02/S03을 함께 재실행하도록 역산했다.

### 테스트 환경 규칙

- `test_env=local`
- `agent-test/local/rules.md`를 읽었다. `agent-ops/skills/project/**`에 매칭되는 추가 profile route는 없다.
- 외부 runner, provider, secret, port를 사용하지 않는다.
- fallback은 저장소의 Python `unittest`와 `py_compile`이다. test-rule 유지보수는 필요하지 않다.
- 현재 증거: selector/policy 통합 25개와 대상 파일 `py_compile`이 PASS했다.

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

- selected 누락, scalar decision, non-object quota와 invalid quota target entry는 기존 회귀가 커버한다.
- object/list container가 유효하지만 candidate entry, decision field, quota field 또는 enum이 불완전한 prior decision은 회귀가 없다.
- 정상 initial/resume, KST 네 경계, worker/review grade matrix는 기존 테스트가 커버한다.

### 심볼 참조

- renamed/removed symbol: none

### 분할 판단

- split 정책을 파일 선택 전에 적용했다.
- 기존 `01_selector_contract` 안의 하나의 prior decision validator와 그 전용 테스트만 수정한다. API-vs-call-site rollout, 다른 ownership, 별도 검증 환경이 없고 production validator와 회귀 테스트를 분리하면 독립 완료 가치가 없으므로 단일 follow-up이 안전하다.
- sibling `02+01_quota_input`은 이 subtask의 `complete.log`를 기다리며 현재 수정 범위와 병행하지 않는다.

### 범위 결정 근거

- `quota_snapshot`의 required-cap tri-state와 admission, dispatcher persisted state, failover, failure budget은 후속 subtask/Epic 소유라 수정하지 않는다.
- `execution_target_policy.py`의 시간/grade matrix와 `orchestrate-agent-task-loop/SKILL.md`의 runtime route 표면은 변경하지 않는다.
- 미래 호환 추가 key는 허용하되, 현재 initial schema가 생성하는 필수 field·type·enum만 검증한다.
- `agent-spec/index.md`와 `agent-contract/index.md`에는 이 내부 selector 계약에 매칭되는 living spec/계약 문서가 없어 별도 문서를 읽거나 갱신하지 않는다.

### 최종 라우팅

- evaluation_mode: `isolated-reassessment`
- finalizer: `finalize-task-policy.sh`
- finalizer_mode: `pair`
- build closures: scope/context/verification/evidence/ownership/decision 모두 `true`
- build closure basis: 한 selector validator와 전용 회귀로 범위가 닫히고, 세 가지 현재 성공 재현과 local unittest로 결과를 결정적으로 판별하며, 사용자 결정이나 외부 환경이 없다.
- build base route: `local-G04`
- build grade scores: scope_coupling=1, state_concurrency=1, blast_irreversibility=1, evidence_diagnosis=0, verification_complexity=1
- build loop-risk: `boundary_contract`, `structured_interpretation`, `variant_product`가 매칭되어 `triggered: true`; cloud minimum `G07` 적용
  - `boundary_contract`: persisted prior schema를 direct API와 CLI/향후 dispatcher consumer가 함께 사용하며 validation/compatibility를 유지해야 한다.
  - `structured_interpretation`: 중첩 object/list field와 enum 해석에서 누락·잘못된 타입을 구분해야 한다.
  - `variant_product`: candidate/decision/quota 입력군과 direct/CLI 표면의 정상·누락·오류 형식 조합을 검증한다.
- build final: `cloud-G07`, `PLAN-cloud-G07.md`
- review closures: scope/context/verification/evidence/ownership/decision 모두 `true`
- review closure basis: SDD S01, initial output schema, validator source, 집중 재현과 전체 unittest를 한 checkout에서 대조할 수 있다.
- review base route: `local-G05`
- review grade scores: scope_coupling=1, state_concurrency=1, blast_irreversibility=1, evidence_diagnosis=1, verification_complexity=1
- review capability floor: official review minimum `cloud-G09`
- review final: `cloud-G09`, `CODE_REVIEW-cloud-G09.md`

## 구현 체크리스트

- [ ] prior decision의 `candidates`, `decision`, `quota` 필수 하위 field·type·enum을 initial 출력 schema 기준으로 검증하고 모든 위반을 `malformed_prior_decision`으로 정규화하며, direct API와 CLI 회귀 테스트가 focused selector suite에서 통과하도록 한다.
- [ ] `python3 -m unittest discover -s agent-ops/skills/project/orchestrate-agent-task-loop/tests -p 'test_*execution_target*.py'`와 대상 파일 `py_compile`을 실행해 S01~S03 전체 회귀가 통과함을 확인한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

### [REVIEW_API-1] Prior decision 전체 중첩 schema 검증

#### 문제

`agent-ops/skills/project/orchestrate-agent-task-loop/scripts/select_execution_target.py:175`은 `candidates`가 list인지, `decision`과 `quota`가 object인지만 확인한다. 내부 field가 없거나 잘못된 enum이어도 `_resume()`이 그대로 복사하므로 완전한 initial schema와 다른 성공 JSON을 만든다.

현재 기준:

```python
# select_execution_target.py:175
if not isinstance(value["candidates"], list):
    raise SelectorInputError(...)
if not isinstance(value["decision"], dict):
    raise SelectorInputError(...)
if not isinstance(value["quota"], dict):
    raise SelectorInputError(...)
```

#### 해결 방법

initial 출력이 만드는 필수 schema를 작은 validator로 분리해 resume 재사용 전에 모두 검증한다.

```python
def _validate_prior_candidates(candidates: object) -> None:
    # non-empty list, consecutive positive rank, target/type/quota/eligibility fields
    ...


def _validate_prior_decision_evidence(decision: object) -> None:
    # rule_id, policy_priority, reason_codes, evaluated_at, timezone,
    # time_window, pinned
    ...


def _validate_prior_quota(quota: object) -> None:
    # snapshot_id, mode, status, source, checked_at
    ...
```

- candidate는 `candidate_rank`, `adapter`, `target`, `execution_class`, `selfcheck_required`, `quota_mode`, `quota_status`, `eligibility`, `rejection_reason`의 필수 field와 타입을 검사하고 rank가 1부터 연속인지 확인한다.
- decision은 `rule_id`, `policy_priority`, `reason_codes`, `evaluated_at`, `timezone`, `time_window`, `pinned`의 필수 field와 타입을 검사한다. `timezone`은 `Asia/Seoul`, enum field는 현재 출력 허용값만 받는다.
- quota는 `snapshot_id`, `mode`, `status`, `source`, `checked_at`의 필수 field와 nullable/string 타입 및 현재 enum을 검사한다.
- 추가 key는 미래 호환을 위해 허용한다. 모든 위반은 `SelectorInputError("malformed_prior_decision", ...)`로 정규화한다.

#### 수정 파일 및 체크리스트

- [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/select_execution_target.py` — candidates/decision/quota 하위 schema validator를 추가한다.
- [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_select_execution_target.py` — 유효 container 안의 누락 field·잘못된 type/enum을 direct/CLI에서 거부하는 회귀를 추가한다.

#### 테스트 작성

작성한다.

- `test_resume_rejects_malformed_nested_prior_schema_variants`: initial decision을 복제해 empty candidate, 누락 decision field, 잘못된 quota enum과 대표 type 오류를 주입하고 모두 `malformed_prior_decision`인지 table-driven 방식으로 확인한다.
- `test_cli_deeply_malformed_prior_uses_json_error_envelope`: container는 유효하지만 하위 field가 잘못된 prior JSON이 stdout 없이 exit 2/stderr JSON인지 확인한다.
- fixture는 `TemporaryDirectory`의 task/prior JSON만 사용한다.

#### 중간 검증

```bash
python3 -m unittest discover -s agent-ops/skills/project/orchestrate-agent-task-loop/tests -p 'test_select_execution_target.py'
```

기대 결과: 정상 initial/resume은 유지되고 candidates/decision/quota의 모든 계획된 malformed variant가 안정적인 selector 오류로 PASS한다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/select_execution_target.py` | REVIEW_API-1 |
| `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_select_execution_target.py` | REVIEW_API-1 |

## 최종 검증

```bash
python3 -m unittest discover -s agent-ops/skills/project/orchestrate-agent-task-loop/tests -p 'test_*execution_target*.py'
python3 -m py_compile \
  agent-ops/skills/project/orchestrate-agent-task-loop/scripts/execution_target_policy.py \
  agent-ops/skills/project/orchestrate-agent-task-loop/scripts/select_execution_target.py \
  agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_execution_target_policy.py \
  agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_select_execution_target.py
```

기대 결과: S01의 완전한 prior schema와 explicit input error, S02/S03 시간·grade matrix가 모두 PASS하고 대상 Python 파일이 compile된다. unittest cache는 해당 없다.

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