<!-- task=01_pool_schema plan=1 tag=REVIEW_API -->
# Plan - REVIEW_API

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

이 plan은 직전 code-review `FAIL`의 Required 항목만 좁게 처리한다. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 선택된 SDD 결정 또는 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실제 구현을 차단할 때만 `CODE_REVIEW-local-G06.md`의 `사용자 리뷰 요청` 섹션을 채우고 중단한다. 환경/secret/서비스 준비, 검증 증거 공백, 일반 범위 조정은 사용자 리뷰 요청이 아니다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/provider-catalog-device-status.md`
- Task ids:
  - `alias-contract`: `models[].id` canonical routing key와 `models[].providers` provider id -> served model map 검증
  - `candidate-schema`: `nodes[].providers[]` provider id/type/category/served models/health/capacity/in-flight/queued/coarse lifecycle 표현
- Completion mode: check-on-pass

## Spec Targets

- SDD: `agent-roadmap/sdd/operational-observability-provider-management/provider-catalog-device-status/SDD.md`
- Acceptance Scenarios:
  - `S01`: model alias provider pool contract
  - `S02`: provider candidate schema
- Evidence Map:
  - `S01`, `S02`는 이 task directory의 `complete.log`에 Roadmap Completion과 Spec Completion 근거가 있어야 한다.

## Archive Evidence Snapshot

- Archived plan: `agent-task/m-provider-catalog-device-status/01_pool_schema/plan_local_G06_0.log`
- Archived review: `agent-task/m-provider-catalog-device-status/01_pool_schema/code_review_local_G06_0.log`
- Verdict: FAIL
- Required summary:
  - `packages/go/config/config.go` validates provider id existence but not that `models[].providers[provider_id]` served model belongs to the referenced provider's `models[]`.
  - `apps/edge/internal/service/model_queue.go` uses `prov.ID` as the queue stats key, so provider snapshots lose `in_flight`, `queued`, and `load_ratio` when provider id differs from adapter/instance key.
  - `configs/edge.yaml` contains migration comments but no actual top-level `models[]` / `nodes[].providers[]` example required by the plan.
- Verification evidence from previous loop:
  - `make proto`: passed.
  - `go test ./packages/go/config ./apps/edge/internal/openai ./apps/edge/internal/service ./apps/edge/internal/node -count=1`: passed.
  - `go test ./...`: passed.
  - `git diff --check`: passed.
- Affected files for this follow-up:
  - `packages/go/config/config.go`
  - `packages/go/config/config_test.go`
  - `apps/edge/internal/service/model_queue.go`
  - `apps/edge/internal/service/status_provider_test.go` or `apps/edge/internal/service/model_queue_test.go`
  - `configs/edge.yaml`
- Narrow archive reread allowed if needed: the two archived files listed above only.

## 범위 결정 근거

- 후속 범위는 code-review Required 3건으로 제한한다.
- `proto/iop/runtime.proto`와 생성물은 직전 검증에서 최신으로 확인되었고, 이번 follow-up은 proto field를 추가하지 않는다.
- SDD/Roadmap Target은 그대로 유지한다. S01/S02 completion은 membership validation, provider status projection, config example까지 충족된 뒤 PASS 시 `complete.log`에 복사된다.
- Roadmap 문서 상태 갱신이나 archive는 이 plan 범위가 아니다.

## 구현 체크리스트

- [ ] [REVIEW_API-1] `models[].providers[provider_id]` served model이 해당 `nodes[].providers[].models` list에 포함되는지 load-time validation과 회귀 테스트를 추가한다.
- [ ] [REVIEW_API-2] provider catalog snapshot의 `in_flight`, `queued`, `load_ratio`가 provider id와 adapter/instance key가 다를 때도 실제 queue state를 반영하도록 수정하고 service/status 테스트를 추가한다.
- [ ] [REVIEW_API-3] `configs/edge.yaml`에 loader가 지원하는 top-level `models[]`와 `nodes[].providers[]` commented example을 추가하고 legacy `openai.model_routes` migration 문구와 일치시킨다.
- [ ] 중간 및 최종 검증 명령을 실행하고 결과를 `CODE_REVIEW-local-G06.md`에 붙여 넣는다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## REVIEW_API-1 - served model membership validation

### 문제

현재 `ModelCatalogEntry.Validate`는 provider id가 존재하는지만 보고, `models[].providers[provider_id]` 값이 해당 provider의 `models[]` list에 있는지 확인하지 않는다. SDD S01/S02와 원 plan은 provider id -> served model map이 provider별 served model list와 일치해야 한다고 요구한다.

### 변경

`LoadEdge`에서 provider id별 served model set을 구성한다. `models[]` validation은 provider id resolve와 함께 served model membership을 검사해야 한다. provider id가 중복될 수 있는 설계가 필요하다면 top-level model map에서 node identity 없이 disambiguate할 수 없으므로, 이 follow-up에서는 provider id를 catalog reference 기준으로 유일하게 다루거나 명확한 에러를 반환한다.

테스트는 최소 아래를 포함한다.

- happy path가 기존처럼 통과한다.
- `models[].providers.vllm-gpu: "typo-model"`이고 `nodes[].providers[id=vllm-gpu].models`에 없는 경우 `LoadEdge`가 실패한다.
- 에러 메시지는 model id, provider id, missing served model을 식별할 수 있어야 한다.

### 중간 검증

```bash
go test ./packages/go/config -run 'TestLoadEdge_ModelCatalog|TestModelCatalogEntry|TestNodeProviderConf' -count=1
```

## REVIEW_API-2 - provider snapshot queue stats

### 문제

새 provider snapshot은 `getStatsForAdapterLocked(nodeID, rec, prov.ID)`로 queue 통계를 조회한다. 기존 queue group은 adapter/target을 canonical adapter instance name으로 resolve하므로, provider id가 adapter/instance key와 다르면 snapshot의 `InFlight`, `Queued`, `LoadRatio`가 실제 상태를 반영하지 않는다.

### 변경

provider snapshot의 stats key를 provider id와 adapter/instance identity의 책임에 맞게 정리한다. 최소 기준은 `prov.Adapter`가 있으면 그 값을 queue stats lookup에 사용하고, 없으면 기존 provider id fallback을 사용한다. 기존 named adapter instance resolve와 충돌하지 않게 `resolveSnapshotAdapterName` 경로를 재사용하거나 작은 helper를 추가한다.

테스트는 provider id와 adapter key가 다른 fixture를 사용한다.

- 예: provider id `provider-vllm-primary`, adapter/instance key `vllm-gpu`.
- queue group에 `adapter: "vllm-gpu"` 또는 기존 dispatch가 만드는 canonical adapter key를 넣는다.
- `ListNodeSnapshots()` 또는 queue snapshot helper가 provider snapshot의 `Id`, `Adapter`, `InFlight`, `Queued`, `LoadRatio`를 기대값으로 반환하는지 확인한다.

### 중간 검증

```bash
go test ./apps/edge/internal/service -run 'TestListNodeSnapshots|TestModelQueue|TestProvider' -count=1
```

## REVIEW_API-3 - `configs/edge.yaml` provider pool example

### 문제

`configs/edge.yaml`는 migration 방향을 주석으로 설명하지만, 실제 top-level `models[]`와 `nodes[].providers[]` schema 예시가 없다. README와 달리 기본 config 예시만 보는 운영자는 새 schema의 필드 이름과 placement를 확인할 수 없다.

### 변경

기본 config를 실행값으로 오염시키지 말고 commented example로 추가한다.

- top-level `models:` example은 `id`, `display_name`, `providers` map을 보여준다.
- `nodes[].providers[]` example은 `id`, `type`, `category`, `adapter`, `models`, `health`, `capacity`, `max_queue`, `queue_timeout_ms`, `lifecycle_capabilities`를 보여준다.
- legacy `openai.model_routes`는 fallback/compat route catalog라는 설명을 유지한다.

### 중간 검증

```bash
rg --sort path -n 'models\\[\\]|nodes\\[\\]\\.providers|provider-pool|model_routes|lifecycle_capabilities' configs/edge.yaml apps/edge/README.md
```

## 최종 검증

```bash
go test ./packages/go/config ./apps/edge/internal/service ./apps/edge/internal/openai ./apps/edge/internal/node -count=1
go test ./...
git diff --check
```

기대 결과: provider pool config validation과 provider status projection 테스트가 통과하고, 전체 Go 회귀와 whitespace 검증이 통과한다.
