<!-- task=01_pool_schema plan=2 tag=REVIEW_REVIEW_API -->
# Plan - REVIEW_REVIEW_API

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

이 plan은 직전 code-review `FAIL`의 Required 2건만 좁게 처리한다. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `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 history:
  - `agent-task/m-provider-catalog-device-status/01_pool_schema/plan_local_G06_0.log`
  - `agent-task/m-provider-catalog-device-status/01_pool_schema/plan_local_G06_1.log`
- Archived review history:
  - `agent-task/m-provider-catalog-device-status/01_pool_schema/code_review_local_G06_0.log`
  - `agent-task/m-provider-catalog-device-status/01_pool_schema/code_review_local_G06_1.log`
- Latest verdict: FAIL
- Required summary:
  - `packages/go/config/config.go` still permits the same provider id on different nodes. Because `models[].providers` is a top-level provider id map, this makes catalog references ambiguous and merges served model sets.
  - `apps/edge/internal/service/model_queue.go` does not fallback to provider id when `nodes[].providers[].adapter` is empty; stats lookup uses an empty key and drops queue state.
- Verification evidence from previous loop:
  - `go test ./packages/go/config -run 'TestLoadEdge_ModelCatalog|TestModelCatalogEntry|TestNodeProviderConf' -count=1`: passed.
  - `go test ./apps/edge/internal/service -run 'TestListNodeSnapshots|TestModelQueue|TestProvider' -count=1`: passed.
  - `go test ./packages/go/config ./apps/edge/internal/service ./apps/edge/internal/openai ./apps/edge/internal/node -count=1`: passed.
  - `go test ./...`: passed.
  - `git diff --check`: passed.
  - `make proto`: 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 adjacent service test
- Narrow archive reread allowed if needed: the four archived files listed above only.

## 범위 결정 근거

- 후속 범위는 code-review Required 2건으로 제한한다.
- `configs/edge.yaml`, README, proto field generation, and the first-loop membership validation are not reopened unless the narrow fixes require a direct consistency update.
- Roadmap/SDD Target은 그대로 유지한다. S01/S02 completion은 provider id reference determinism and status projection fallback까지 충족된 뒤 PASS 시 `complete.log`에 복사된다.
- Roadmap 문서 상태 갱신이나 archive는 이 plan 범위가 아니다.

## 구현 체크리스트

- [ ] [REVIEW_REVIEW_API-1] `nodes[].providers[].id`가 전체 Edge config에서 catalog reference 기준으로 유일하도록 validation과 전역 중복 회귀 테스트를 추가한다.
- [ ] [REVIEW_REVIEW_API-2] provider snapshot stats key가 `adapter` 미설정 시 provider id로 fallback하도록 수정하고 adapter 없는 provider fixture로 `InFlight`, `Queued`, `LoadRatio` 회귀 테스트를 추가한다.
- [ ] 중간 및 최종 검증 명령을 실행하고 결과를 `CODE_REVIEW-local-G06.md`에 붙여 넣는다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## REVIEW_REVIEW_API-1 - provider id global uniqueness

### 문제

`models[].providers`는 provider id만 key로 가진 top-level map이다. 같은 provider id가 여러 Node에 존재하면 어느 Node provider를 참조하는지 구분할 수 없다. 현재 `LoadEdge`는 node 내부 중복만 막고, 전역 `providerIDs` map과 served model index는 중복 id를 합쳐 버린다.

### 변경

`LoadEdge`에서 provider id를 전체 `nodes[].providers[]` 기준으로 유일하게 검증한다. 같은 provider id가 두 노드에 나오면 node index 또는 node id/alias와 provider id를 포함한 명확한 에러를 반환한다. 이 변경은 `models[].providers`의 결정성을 보장하기 위한 validation foundation이다.

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

- 두 노드가 같은 provider id를 갖는 config가 실패한다.
- 에러 메시지는 duplicate provider id와 양쪽 노드 위치를 판단할 수 있어야 한다.
- 기존 같은 노드 내부 중복 테스트도 계속 통과한다.

### 중간 검증

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

## REVIEW_REVIEW_API-2 - provider snapshot adapter fallback

### 문제

`nodes[].providers[].adapter`가 비어 있어도 config validation은 통과한다. 그런데 provider snapshot stats key는 `resolveSnapshotAdapterName(rec, prov.Adapter, prov.ID)` 결과를 그대로 써서, empty adapter인 경우 provider id fallback이 아니라 빈 문자열로 queue state를 조회한다.

### 변경

provider snapshot stats key helper를 명확히 만든다.

- `strings.TrimSpace(prov.Adapter) != ""`이면 기존 adapter/instance resolve path를 사용한다.
- adapter가 비어 있으면 `prov.ID`를 stats key로 사용한다.
- provider id와 adapter key가 다른 기존 테스트는 유지한다.
- adapter field가 없는 provider fixture에서 queue group `adapter`가 provider id인 경우 `InFlight`, `Queued`, `LoadRatio`가 반영되는 테스트를 추가한다.

### 중간 검증

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

## 최종 검증

```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 id reference determinism과 provider snapshot fallback 테스트가 통과하고, 전체 Go 회귀와 whitespace 검증이 통과한다.
