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

# Selector 중첩 JSON 입력 계약 보강

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

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

## 배경

첫 구현은 정상 selector 경로와 S01~S03 route matrix를 통과했지만, prior decision과 quota snapshot의 중첩 JSON 형식을 충분히 검증하지 않았다. 그 결과 불완전한 resume schema가 성공하거나 malformed 입력이 계약된 오류 envelope 대신 traceback을 낸다. 기존 정책 matrix와 quota 정규화 책임은 유지하면서 selector 입력 경계만 보강한다.

## 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_0.log`
- Archived review: `agent-task/m-agent-task-runtime-target-selector/01_selector_contract/code_review_cloud_G09_0.log`
- Verdict: `FAIL`
- Findings: Required 1, Suggested 0, Nit 0
- Required: malformed `prior_decision`/`quota_snapshot` 중첩 형식이 불완전한 성공 schema 또는 traceback/exit 1을 만든다.
- 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 17개와 selector/policy 통합 22개는 PASS했다. 집중 재현은 incomplete selected가 `adapter,target`만 가진 채 성공하고 malformed decision 및 배열 quota가 traceback/exit 1을 내는 것을 확인했다.
- Roadmap carryover: `selector-contract`, `time-route`, `grade-route`; SDD S01의 완전한 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`

### 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의 prior/quota 중첩 입력 schema와 오류 envelope를 보강하고, 최종 검증에서 S02/S03 matrix를 함께 재실행하도록 역산했다.

### 테스트 환경 규칙

- `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 17개, selector/policy 통합 22개와 대상 파일 `py_compile`이 PASS했다.

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

- incomplete `prior_decision.selected`가 안정적인 output schema를 깨는 경우가 없다.
- non-object 또는 잘못 중첩된 `prior_decision`/`quota_snapshot`이 CLI stderr JSON/exit 2로 정규화되는 회귀 테스트가 없다.
- 정상 initial/resume, KST 경계, worker/review grade matrix는 기존 테스트가 커버한다.

### 심볼 참조

- renamed/removed symbol: none

### 분할 판단

- split 정책을 파일 선택 전에 적용했다.
- 기존 `01_selector_contract` 안의 단일 selector 입력 경계와 그 전용 테스트만 수정한다. API와 call-site rollout, 도메인, 검증 환경, 독립 ownership 경계가 새로 갈리지 않으므로 추가 subtask는 인위적이다.
- sibling `02+01_quota_input`은 이 subtask의 `complete.log`를 기다리며 현재 수정 범위와 병행하지 않는다.

### 범위 결정 근거

- `execution_target_policy.py`의 시간/grade matrix는 변경하지 않는다.
- `dispatch.py`의 persisted state, failover, failure budget과 skill route 표면은 후속 Epic 범위라 수정하지 않는다.
- Go usage checker 호출과 required-cap tri-state 정규화는 `02+01_quota_input` 소유라 구현하지 않는다.
- malformed JSON container/schema를 selector 입력 오류로 정규화하되 quota admission 또는 target 재선택은 추가하지 않는다.

### 최종 라우팅

- 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 모듈과 전용 테스트로 범위가 닫히고, 재현 입력과 local unittest로 결과를 결정적으로 검증하며, dependent sibling은 완료 전 대기한다.
- 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`: CLI decoder, selector API와 향후 dispatcher consumer가 같은 validation/compatibility 계약을 소비한다.
  - `structured_interpretation`: 중첩 JSON object/list/type 해석과 malformed 입력 정규화가 필요하다.
  - `variant_product`: prior/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, selector source, focused reproducer와 전체 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과 quota snapshot의 필수 중첩 필드·타입·허용값을 검증하고 모든 위반을 `SelectorInputError`로 정규화하며, 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/quota 중첩 JSON schema 검증

#### 문제

`agent-ops/skills/project/orchestrate-agent-task-loop/scripts/select_execution_target.py:100`은 `quota_snapshot.get()`과 entry `.get()`을 컨테이너 타입 확인 없이 호출한다. `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/select_execution_target.py:263`은 prior `selected`에서 `adapter`/`target`만 확인하고 `decision`, `candidates`, `quota`를 검증하지 않은 채 복사한다.

현재 기준:

```python
# select_execution_target.py:263
selected = prior_decision["selected"]
if (
    not isinstance(selected, dict)
    or "adapter" not in selected
    or "target" not in selected
):
    ...
decision = dict(prior_decision["decision"])
```

이 때문에 selected 필드 누락은 성공 schema에 전파되고, `decision=1` 또는 배열형 quota snapshot은 `main()`의 계약 오류 처리에 도달하지 못한 `TypeError`/`AttributeError` traceback을 만든다.

#### 해결 방법

`prior_decision`과 `quota_snapshot`에 명시적인 구조 validator를 둔다.

- prior decision은 현재 schema version, identity/stage/lane/grade, `selected`, `candidates`, `decision`, `quota`의 필수 container·field·type과 enum을 검증한다. 미래 호환용 추가 key는 허용한다.
- resume에 필요한 `selected.execution_class`와 `selected.selfcheck_required`를 필수로 하여 성공 출력 schema가 initial과 동일하게 유지되게 한다.
- quota snapshot이 제공되면 object, `targets` list와 각 target entry의 adapter/target/status 형식을 검증한다. 실제 required-cap 정규화나 admission은 추가하지 않는다.
- 모든 schema 위반은 `SelectorInputError`의 안정적인 error code로 변환해 CLI가 stdout 없이 stderr JSON과 exit 2를 반환하게 한다.

예상 형태:

```python
def _validate_prior_decision(value: object) -> dict:
    if not isinstance(value, dict):
        raise SelectorInputError("malformed_prior_decision", ...)
    ...
    return value


def _validate_quota_snapshot(value: object | None) -> dict | None:
    if value is None:
        return None
    if not isinstance(value, dict):
        raise SelectorInputError("malformed_quota_snapshot", ...)
    ...
    return value
```

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

- [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/select_execution_target.py` — prior/quota nested schema validator와 오류 정규화를 추가한다.
- [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_select_execution_target.py` — 불완전 selected, scalar decision, non-object/invalid quota entry의 direct/CLI 회귀를 추가한다.

#### 테스트 작성

작성한다.

- `test_resume_rejects_incomplete_selected_schema`: 필수 selected 필드 누락이 `malformed_prior_decision`인지 확인한다.
- `test_cli_malformed_prior_uses_json_error_envelope`: scalar nested decision이 stdout 없이 exit 2/stderr JSON인지 확인한다.
- `test_cli_malformed_quota_uses_json_error_envelope`: 배열 snapshot과 invalid target entry가 stdout 없이 exit 2/stderr JSON인지 확인한다.
- fixture는 `TemporaryDirectory`의 task/prior/quota JSON만 사용한다.

#### 중간 검증

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

기대 결과: 정상 initial/resume은 유지되고 모든 malformed nested input 회귀가 안정적인 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 nested input/error envelope와 S02/S03 시간·grade matrix가 모두 PASS하고 대상 Python 파일이 compile된다. unittest cache는 해당 없다.

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