<!-- task=m-model-group-mixed-provider-dispatch/01-provider-path-classifier plan=1 tag=REVIEW_MIXED_CLASSIFIER -->

# PLAN-local-G06: Review Follow-up - Provider Candidate Presence Assertions

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

`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우는 것이 구현의 마지막 필수 단계다. 검증을 실행하고, active plan/review 파일을 유지한 채 리뷰 준비 상태를 보고한다. 종결 처리, log rename, `complete.log`, archive 이동은 code-review 에이전트 전용이다.

선택된 Milestone의 `구현 잠금 > 결정 필요` 항목이 실제 구현을 막는 경우에만 대응 review stub의 `사용자 리뷰 요청` 섹션에 연결 근거를 채우고 멈춘다. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 환경/secret/외부 서비스 준비, 일반 scope 조정, 검증 증거 공백은 사용자 리뷰 요청이 아니라 검증 결과나 follow-up plan으로 남긴다.

## 배경

첫 번째 구현은 provider 후보에 execution path metadata를 추가하고 classifier 테스트를 작성했지만, 리뷰에서 `TestResolveProviderPoolCandidatesClassifiesExecutionPath`가 반환된 후보만 순회한다는 문제가 발견됐다. 이 상태에서는 기대 provider가 후보에서 빠져도 테스트가 실패하지 않아 SDD S04의 "Ollama/CLI/native provider가 normalized 후보로 남는다" 증거가 충분하지 않다.

## 사용자 리뷰 요청 흐름

사용자 리뷰 요청은 선택된 Milestone lock 결정이 구현을 차단할 때만 active `CODE_REVIEW-*-G??.md`의 `사용자 리뷰 요청` 섹션에 기록한다. 해당 섹션은 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식을 따른다. 구현 에이전트는 사용자에게 직접 묻지 않고, code-review 에이전트가 요청 타당성을 검증한 뒤 필요할 때만 실제 `USER_REVIEW.md`를 작성한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/routing-policy-model-orchestration/milestones/model-group-mixed-provider-dispatch.md`
- Milestone link: [Milestone 문서](agent-roadmap/phase/routing-policy-model-orchestration/milestones/model-group-mixed-provider-dispatch.md)
- Task ids:
  - `provider-path-classifier`: Provider classifier가 OpenAI-compatible 호출 방식을 지원하는 모든 provider를 passthrough 방식으로, Ollama/CLI/native provider를 normalized로 분류한다. Seulgivibe aliases는 passthrough provider에 포함한다.
- Completion mode: check-on-pass

## Archive Evidence Snapshot

- Archived plan log: `agent-task/m-model-group-mixed-provider-dispatch/01-provider-path-classifier/plan_local_G06_0.log`
- Archived review log: `agent-task/m-model-group-mixed-provider-dispatch/01-provider-path-classifier/code_review_local_G06_0.log`
- Verdict: FAIL
- Required summary: `TestResolveProviderPoolCandidatesClassifiesExecutionPath` checks execution path only for candidates that are returned; it does not fail when an expected tunnel or normalized provider is missing from the candidate set.
- Affected files: `apps/edge/internal/service/model_queue_test.go`
- Verification evidence: archived review recorded passing `GOCACHE=/tmp/iop-go-cache go test ./packages/go/config -count=1`, `GOCACHE=/tmp/iop-go-cache go test ./apps/edge/internal/node -count=1`, `GOCACHE=/tmp/iop-go-cache go test ./apps/node/internal/adapters -count=1`, and `GOCACHE=/tmp/iop-go-cache go test ./apps/edge/internal/service -count=1`; reviewer reran the same four commands and they passed.
- Roadmap carryover: keep the same `provider-path-classifier` Roadmap Target and SDD S03/S04 evidence expectation.
- Narrow archive reread allowed if needed: `agent-task/m-model-group-mixed-provider-dispatch/01-provider-path-classifier/plan_local_G06_0.log`, `agent-task/m-model-group-mixed-provider-dispatch/01-provider-path-classifier/code_review_local_G06_0.log`.

## 분석 결과

### 읽은 파일

- `agent-task/m-model-group-mixed-provider-dispatch/01-provider-path-classifier/plan_local_G06_0.log`
- `agent-task/m-model-group-mixed-provider-dispatch/01-provider-path-classifier/code_review_local_G06_0.log`
- `apps/edge/internal/service/model_queue_test.go`

### SDD 기준

- SDD: `agent-roadmap/sdd/routing-policy-model-orchestration/model-group-mixed-provider-dispatch/SDD.md`
- 대상 Acceptance Scenario: S03, S04
- 핵심 보강: expected provider가 모두 후보에 남고, 각 provider의 `executionPath`가 기대 path와 일치한다는 assertion이 필요하다.

### 범위 결정 근거

- production classifier 구현은 유지한다.
- 이번 후속은 service test evidence를 신뢰 가능하게 만드는 보강만 수행한다.
- `agent-task/archive/**`, `agent-roadmap/archive/**`, `agent-spec/archive/**`는 읽지 않는다.

### 빌드 등급

- `local-G06`: 수정 범위는 단일 Go service test지만, Milestone/SDD evidence와 code-review artifact 재종결이 연결되어 있어 기존 local-G06 경로를 유지한다.

## 구현 체크리스트

- [x] `TestResolveProviderPoolCandidatesClassifiesExecutionPath`가 expected tunnel/normalized provider 전체의 존재와 execution path를 모두 검증하도록 보강한다. 검증: `GOCACHE=/tmp/iop-go-cache go test ./apps/edge/internal/service -count=1`. PASS.
- [x] Roadmap evidence를 위해 기존 alias 회귀 검증 명령을 재실행하고 실제 stdout/stderr를 review stub에 기록한다. 검증: `GOCACHE=/tmp/iop-go-cache go test ./packages/go/config -count=1`, `GOCACHE=/tmp/iop-go-cache go test ./apps/edge/internal/node -count=1`, `GOCACHE=/tmp/iop-go-cache go test ./apps/node/internal/adapters -count=1`. PASS.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_MIXED_CLASSIFIER-1] Candidate Presence Assertions

#### 문제

`apps/edge/internal/service/model_queue_test.go`의 `TestResolveProviderPoolCandidatesClassifiesExecutionPath`는 `for _, c := range candidates`로 반환 후보만 검사한다. 따라서 expected provider가 후보에서 누락되어도 테스트가 통과할 수 있다.

#### 해결 방법

반환 후보를 `map[string]providerExecutionPath`로 변환하고, expected tunnel/normalized map 전체를 순회하면서 각 provider id의 존재와 path를 함께 검증한다. 총 후보 수 또는 unexpected provider assertion도 유지해 중복/누락이 보이게 한다.

Before:

```go
for _, c := range candidates {
    _, okTunnel := expectedTunnel[c.providerID]
    _, okNorm := expectedNormalized[c.providerID]
    // returned candidates only
}
```

After:

```go
got := map[string]providerExecutionPath{}
for _, c := range candidates {
    got[c.providerID] = c.executionPath
}
for providerID := range expectedTunnel {
    path, ok := got[providerID]
    // require ok and tunnel path
}
for providerID := range expectedNormalized {
    path, ok := got[providerID]
    // require ok and normalized path
}
```

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

- [x] `apps/edge/internal/service/model_queue_test.go`: candidate presence/path assertion 보강.

#### 테스트 작성

- 기존 `TestResolveProviderPoolCandidatesClassifiesExecutionPath`를 보강한다.
- 별도 production code 변경은 예상하지 않는다.

#### 중간 검증

```bash
GOCACHE=/tmp/iop-go-cache go test ./apps/edge/internal/service -count=1
```

기대 결과: PASS.

## 수정 파일 요약

| 파일 | 항목 |
| --- | --- |
| `apps/edge/internal/service/model_queue_test.go` | REVIEW_MIXED_CLASSIFIER-1 |

## 최종 검증

```bash
$ GOCACHE=/tmp/iop-go-cache go test ./packages/go/config -count=1
ok  	iop/packages/go/config	0.036s
$ GOCACHE=/tmp/iop-go-cache go test ./apps/edge/internal/node -count=1
ok  	iop/apps/edge/internal/node	0.004s
$ GOCACHE=/tmp/iop-go-cache go test ./apps/node/internal/adapters -count=1
ok  	iop/apps/node/internal/adapters	0.008s
$ GOCACHE=/tmp/iop-go-cache go test ./apps/edge/internal/service -count=1
ok  	iop/apps/edge/internal/service	0.927s
```

결과: 모든 명령 PASS.

`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션도 함께 작성 완료. 구현 완료.
