<!-- task=m-node-multi-target-serving-foundation/03+01_route_catalog plan=1 tag=REVIEW_API -->

# Code Review Reference - REVIEW_API

> **[IMPLEMENTING AGENT - READ FIRST] Filling in this file is the mandatory final step of implementation.**
> Complete every implementation-owned section, paste real verification output, then stop with active files in place.
> If blocked by a user-only decision or user-owned external prerequisite, fill `사용자 리뷰 요청`; do not ask the user directly.
> Finalization is review-agent-only.

## 개요

date=2026-06-11
task=m-node-multi-target-serving-foundation/03+01_route_catalog, plan=1, tag=REVIEW_API

## Roadmap Targets

- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/node-multi-target-serving-foundation.md`
- Task ids:
  - `route-catalog`: Edge OpenAI-compatible model catalog가 외부 `model`을 Edge-owned route table로 해석하고, 고정 `openai.target` 단일 override 없이 내부 `adapter + target`으로 변환한다.
- Completion mode: check-on-pass

## 이 파일을 읽는 리뷰 에이전트에게

> **[REVIEW AGENT ONLY]** 종결 절차는 코드리뷰 에이전트 전용이다.

`code_review_cloud_G08_0.log`의 Required 이슈 2개가 실제 코드와 테스트로 해소됐는지 확인한다. 특히 duplicate route와 empty target route가 catalog source-of-truth 계약을 깨지 않는지 검증한다.

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [REVIEW_API-1] Ambiguous Route Guard | [x] |
| [REVIEW_API-2] Invalid Route Target Guard | [x] |

## 구현 체크리스트

- [x] duplicate external model id가 ambiguous route로 조용히 처리되지 않도록 검증 또는 오류 처리를 추가한다.
- [x] target이 없는 route entry가 `/v1/models`와 dispatch 계약을 어긋나게 만들지 않도록 검증 또는 필터링 기준을 일치시킨다.
- [x] ambiguous route와 invalid route 테스트를 추가한다.
- [x] 최종 검증 명령을 실행한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 코드리뷰 전용 체크리스트

- [x] `코드리뷰 결과`에 판정을 append한다.
- [x] active `CODE_REVIEW-cloud-G08.md`를 `code_review_cloud_G08_N.log`로 아카이브한다.
- [x] active `PLAN-local-G08.md`를 `plan_local_G08_M.log`로 아카이브한다.
- [x] PASS이면 `complete.log` 작성 후 archive 이동한다.
- [x] WARN/FAIL이면 후속 상태를 작성한다. (PASS라 해당 없음)

## 계획 대비 변경 사항

없음.

## 주요 설계 결정

- **[REVIEW_API-1] 중복 모델 ID**: `LoadEdge`에서 `validateOpenAIRoutes`를 호출해 config load 시점에 즉시 오류를 반환한다. 기존 `checkUniqueNames` 패턴과 동일한 방식. TrimSpace 후 비교하므로 `"model-a"` / `"model-a "` 공백 포함 중복도 거부한다.
- **[REVIEW_API-2] empty target 일치**: `routes.go` `handleModels`에서 `route.Target != ""` 조건을 추가해 `/v1/models` 광고 목록과 `resolveRoute` dispatch 기준이 동일하게 target 비어있는 entry를 제외한다.

## 사용자 리뷰 요청

_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._

- 상태: 없음
- 사유 유형: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음

## 리뷰어를 위한 체크포인트

- duplicate external model id가 first-match dispatch나 duplicate `/v1/models` 노출로 남지 않는다.
- `target`이 없는 route entry가 광고된 model과 실제 dispatch 사이의 불일치를 만들지 않는다.
- chat/responses의 route 우선순위와 legacy fallback은 1차 구현 의도를 유지한다.

## 검증 결과

_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._

필수 규칙:
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다.

### REVIEW_API-1 중간 검증

```
$ go test -count=1 ./packages/go/config
ok  	iop/packages/go/config	0.018s
```

### REVIEW_API-2 중간 검증

```
$ go test -count=1 ./packages/go/config ./apps/edge/internal/openai
ok  	iop/packages/go/config	0.018s
ok  	iop/apps/edge/internal/openai	1.510s
```

### 최종 검증

```
$ go test -count=1 ./packages/go/config ./apps/edge/internal/openai
ok  	iop/packages/go/config	0.018s
ok  	iop/apps/edge/internal/openai	1.510s

$ go test -count=1 ./packages/go/... ./apps/edge/...
ok  	iop/packages/go/audit	0.003s
ok  	iop/packages/go/config	0.033s
ok  	iop/packages/go/hostsetup	0.006s
ok  	iop/packages/go/observability	0.019s
ok  	iop/apps/edge/cmd/edge	0.044s
ok  	iop/apps/edge/internal/bootstrap	0.021s
ok  	iop/apps/edge/internal/controlplane	4.452s
ok  	iop/apps/edge/internal/edgecmd	0.013s
ok  	iop/apps/edge/internal/events	0.003s
ok  	iop/apps/edge/internal/input	0.007s
ok  	iop/apps/edge/internal/input/a2a	0.006s
ok  	iop/apps/edge/internal/node	0.005s
ok  	iop/apps/edge/internal/openai	1.508s
ok  	iop/apps/edge/internal/opsconsole	0.008s
FAIL	iop/apps/edge/internal/service	(pre-existing: TestListNodeSnapshotsWithConfig)
FAIL	iop/apps/edge/internal/transport	(pre-existing: TestBuildConfigPayload_OllamaVllmOneof, TestEdgeServerIntegration)
```

내 변경 범위(`packages/go/config`, `apps/edge/internal/openai`) 모두 PASS. 나머지 실패는 이번 작업 이전부터 존재하는 기존 실패임을 `git stash`로 확인.

## 코드리뷰 결과

- 종합 판정: PASS
- 차원별 평가:
  - correctness: Pass
  - completeness: Pass
  - test coverage: Pass
  - API contract: Pass
  - code quality: Pass
  - plan deviation: Pass
  - verification trust: Pass
- 발견된 문제: 없음
- 리뷰어 검증:
  - `go test -count=1 ./packages/go/config ./apps/edge/internal/openai` PASS.
  - `go test -count=1 ./packages/go/... ./apps/edge/...` FAIL. 실패 패키지는 `apps/edge/internal/service`의 `TestListNodeSnapshotsWithConfig`, `apps/edge/internal/transport`의 `TestBuildConfigPayload_OllamaVllmOneof`, `TestEdgeServerIntegration`로 구현 기록의 pre-existing 실패 목록과 일치한다.
  - `git diff --check -- apps/edge packages/go configs` PASS.
- 다음 단계: PASS이므로 `complete.log` 작성 후 active task 디렉터리를 archive로 이동한다.
