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

# Plan - API

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

구현 완료 전 `CODE_REVIEW-cloud-G08.md`의 구현 에이전트 소유 섹션을 실제 변경 내용과 검증 출력으로 채운다. 사용자 전용 blocker는 review stub의 `사용자 리뷰 요청`에 기록하고, 직접 질문이나 `USER_REVIEW.md` 작성은 하지 않는다.

## 배경

현재 OpenAI-compatible surface는 `openai.target` 단일 override 또는 요청 model 값을 그대로 내부 target으로 사용한다. Edge가 model catalog와 route table을 소유해야 외부 `model`을 내부 `adapter + target + profile`로 안정적으로 변환할 수 있다.

## 사용자 리뷰 요청 흐름

구현 중 차단은 active review stub에 기록한다. code-review가 blocker 타당성과 후속 상태를 결정한다.

## 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

## 분석 결과

### 읽은 파일

- `agent-test/local/rules.md`
- `agent-test/local/edge-smoke.md`
- `packages/go/config/config.go`
- `packages/go/config/config_test.go`
- `apps/edge/internal/openai/routes.go`
- `apps/edge/internal/openai/types.go`
- `apps/edge/internal/openai/chat_handler.go`
- `apps/edge/internal/openai/responses_handler.go`
- `apps/edge/internal/openai/server_test.go`
- `apps/edge/internal/service/run_dispatch.go`
- `apps/edge/internal/service/service_test.go`
- `configs/edge.yaml`

### 테스트 환경 규칙

- test_env: `local`
- edge smoke 기준: config/API 변경은 `go test ./apps/edge/...`, OpenAI surface 변경은 `/healthz`, `/v1/models`, `/v1/chat/completions` 또는 `/v1/responses` smoke 증거가 필요하다. 이 plan은 단위 테스트를 계약으로 두고 live smoke는 field-smoke task로 넘긴다.

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

- `/v1/models`는 `cfg.Models` 또는 단일 `cfg.Target`만 노출한다 (`apps/edge/internal/openai/routes.go:28`).
- chat/responses는 `s.cfg.Target`이 있으면 요청 model과 metadata override를 무시한다 (`apps/edge/internal/openai/chat_handler.go:132`).
- 여러 외부 model이 서로 다른 adapter/target으로 dispatch되는 테스트가 없다.

### 심볼 참조

- rename/remove 예정 심볼: 없음. `EdgeOpenAIConf.Target`, `Models`, `Adapter`, `NodeRef`는 legacy compat로 유지한다.
- route 해석 관련 참조: `handleModels`, `resolveAdapter`, `resolveTargetWithOverride`, `SubmitRunRequest`.

### 분할 판단

- predecessor `01`: `01_config_topology` active plan이 있으며 아직 완료 전이다. 구현은 `01_config_topology/complete.log` 이후 시작한다.
- route catalog는 Edge API surface 변경이므로 Node registry/provider 작업과 분리했다.

### 범위 결정 근거

- 실제 provider별 endpoint/context 실행은 `04+01,02_engine_profiles`와 `05+03,04_edge_owned_options`에서 처리한다.
- live split-host 검증은 `08+03,04,05,06,07_field_smoke`에서 수행한다.

### 빌드 등급

- build: `local-G08`, review: `cloud-G08`. OpenAI-compatible API contract와 Edge routing이 함께 바뀌어 높은 검토가 필요하다.

## 의존 관계 및 구현 순서

- `03+01_route_catalog`는 `01_config_topology` 완료 후 구현한다. 디렉터리 이름에 없는 `02` 의존성은 추가하지 않는다.

## 구현 체크리스트

- [ ] Edge config에 external model catalog와 route table을 추가하고 legacy `openai.target` 동작을 호환한다.
- [ ] `/v1/models`가 route table의 여러 model id를 노출한다.
- [ ] `/v1/chat/completions`와 `/v1/responses`가 외부 model을 내부 adapter/target/node/profile로 변환한다.
- [ ] route miss, ambiguous route, legacy fallback 테스트를 추가한다.
- [ ] 최종 검증 명령을 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [API-1] OpenAI Route Catalog

- 문제: `handleModels`는 route table 없이 문자열 목록만 반환한다 (`apps/edge/internal/openai/routes.go:23`). `resolveTargetWithOverride`는 `cfg.Target`을 전역 override로 우선해 모델별 라우팅을 막는다 (`apps/edge/internal/openai/chat_handler.go:132`). `/v1/responses`도 같은 해석을 사용한다 (`apps/edge/internal/openai/responses_handler.go:51`).
- 해결 방법: `EdgeOpenAIConf`에 `routes` 또는 `model_routes`를 추가해 external model id, node ref, adapter instance, target, session/timeout/profile option reference를 표현한다. handler는 request model을 route catalog로 해석하고, 없을 때만 legacy target/model fallback을 사용한다.
- 수정 파일 및 체크리스트:
  - [ ] `packages/go/config/config.go`: route catalog config type 추가.
  - [ ] `apps/edge/internal/openai/types.go`: request/response model handling 보존.
  - [ ] `apps/edge/internal/openai/routes.go`: `/v1/models` catalog 기반 출력.
  - [ ] `apps/edge/internal/openai/chat_handler.go`: route resolver 적용.
  - [ ] `apps/edge/internal/openai/responses_handler.go`: route resolver 적용.
  - [ ] `apps/edge/internal/service/run_dispatch.go`: 필요한 route metadata 전달.
- 테스트 작성: 추가한다. `server_test.go`에 `/v1/models` multi route, chat dispatch model A/B, responses metadata override와 route 우선순위, missing model 400/404 테스트를 둔다. `config_test.go`에 route catalog decode를 추가한다.
- 중간 검증:
  - `go test -count=1 ./packages/go/... ./apps/edge/internal/openai/... ./apps/edge/internal/service/...`

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `packages/go/config/config.go` | API-1 |
| `packages/go/config/config_test.go` | API-1 |
| `apps/edge/internal/openai/routes.go` | API-1 |
| `apps/edge/internal/openai/types.go` | API-1 |
| `apps/edge/internal/openai/chat_handler.go` | API-1 |
| `apps/edge/internal/openai/responses_handler.go` | API-1 |
| `apps/edge/internal/openai/server_test.go` | API-1 |
| `apps/edge/internal/service/run_dispatch.go` | API-1 |
| `apps/edge/internal/service/service_test.go` | API-1 |
| `configs/edge.yaml` | API-1 |

## 최종 검증

```bash
go test -count=1 ./packages/go/... ./apps/edge/...
```

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
