<!-- task=m-edge-model-group-queue-scheduling/01_edge_queue_service plan=2 tag=REVIEW_REVIEW_REFACTOR -->

# Plan - REVIEW_REVIEW_REFACTOR Edge Queue Service Instance Routing

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

`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션 작성은 필수다. 구현 후 검증 명령을 실행하고 실제 출력, 구현 메모, 계획 대비 변경 사항을 채운 뒤 active 파일을 남긴 채 리뷰 준비 상태로 보고한다. 최종 판정, log rename, `complete.log`, archive 이동은 code-review-skill 전용이다. 구현 중 사용자만 결정할 수 있는 범위 변경, 외부 환경/secret, 또는 scope conflict가 생기면 chat에서 묻지 말고 review stub의 `사용자 리뷰 요청` 섹션에 근거와 재개 조건을 남긴다. 후속 에이전트가 재실행으로 메울 수 있는 증거 공백은 사용자 리뷰 요청 사유가 아니다.

## 배경

두 번째 구현은 provider policy, candidate filtering, disconnect stale candidate, context cancellation을 대부분 보완했다. 남은 결함은 provider adapter를 type 문자열로만 해석해서 Node router의 instance-key/ambiguous-type 계약과 Edge queue 후보 계산이 어긋나는 점이다. 이 follow-up은 provider instance name 기반 routing과 policy 계산만 좁게 고친다.

## 사용자 리뷰 요청 흐름

구현 중 blocker는 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하지 않는다. code-review가 해당 요청을 검증하고 필요할 때만 `USER_REVIEW.md`를 작성한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/edge-model-group-queue-scheduling.md`
- Task ids:
  - `edge-admission`: Edge service가 model group별 FIFO admission queue, capacity, max_queue, queue_timeout, in-flight 상태를 소유한다.
  - `node-dispatch`: Edge가 Node별 실행 가능 slot과 terminal run event를 기준으로 queue head를 다음 Node에 순차 dispatch한다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-ops/skills/common/code-review/SKILL.md`
- `agent-task/m-edge-model-group-queue-scheduling/01_edge_queue_service/plan_cloud_G07_1.log`
- `agent-task/m-edge-model-group-queue-scheduling/01_edge_queue_service/code_review_cloud_G07_1.log`
- `apps/edge/internal/service/run_dispatch.go`
- `apps/edge/internal/service/model_queue.go`
- `apps/edge/internal/service/model_queue_test.go`
- `apps/edge/internal/service/service_test.go`
- `apps/node/internal/router/router.go`
- `apps/node/internal/adapters/registry.go`
- `packages/go/config/config.go`
- `configs/edge.yaml`

### 테스트 환경 규칙

- test_env: `local`.
- 이전 review turn에서 `agent-test/local/rules.md`와 `agent-test/local/edge-smoke.md`를 적용했다.
- 이 follow-up의 필수 명령은 `go test -count=1 ./apps/edge/internal/service`, `go test -race -count=1 ./apps/edge/internal/service`, `go test -count=1 ./apps/edge/...`다.
- focus 검증은 provider instance route와 ambiguous type route 테스트만 먼저 실행한다.

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

- `openai.model_routes[].adapter` 또는 service request adapter가 provider instance key(`ollama-local` 등)일 때 NodeRecord의 matching instance policy/capacity를 쓰는 테스트가 없다.
- provider type route(`ollama`)가 같은 node의 다중 enabled instances와 충돌할 때 Edge가 runnable 후보로 잘못 포함하지 않는 테스트가 없다.

### 심볼 참조

- rename/remove 없음.
- `nodeAdapterCapacity`, `adapterPolicyFields`, `nodeSupportsAdapter`, `resolveQueueCandidates` 내부 의미 변경만 필요하다.

### 분할 판단

- 기존 shared task group은 `m-edge-model-group-queue-scheduling`.
- 현재 subtask는 `01_edge_queue_service`의 second review follow-up이다.
- 남은 issue는 Edge service queue candidate/policy helper 하나의 계약 수정으로 충분하므로 추가 split 없이 plan 2로 이어간다.

### 범위 결정 근거

- OpenAI surface snapshot 문구나 docs 변경은 sibling task 범위로 남긴다.
- Node router/adapters 구현 변경은 하지 않는다. Edge service가 Node router의 현재 exact-instance/type-ambiguity 계약을 반영한다.
- 이 follow-up은 `apps/edge/internal/service` 구현과 tests에 한정한다.

### 빌드 등급

- `cloud-G07`: provider instance routing과 queue policy 계산이 Edge/Node adapter registry 계약을 가로지르는 correctness follow-up이다.

## 구현 체크리스트

- [ ] Provider adapter helper가 request adapter를 exact instance key로 먼저 매칭하고 해당 instance capacity/max_queue/queue_timeout_ms를 사용한다.
- [ ] Provider type-name route는 enabled instance가 정확히 1개일 때만 runnable로 인정하고, 다중 instance type route는 후보 제외 또는 명확한 ambiguity error로 Node router 계약과 맞춘다.
- [ ] instance-key route와 ambiguous type route에 대한 service-level 회귀 테스트를 추가한다.
- [ ] `go test -count=1 ./apps/edge/internal/service`, `go test -race -count=1 ./apps/edge/internal/service`, `go test -count=1 ./apps/edge/...`가 통과한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_REVIEW_REFACTOR-1] Provider Instance Routing

#### 문제

Edge queue helper는 provider adapter를 type 문자열로만 해석한다.

```go
// apps/edge/internal/service/run_dispatch.go:239
239 func nodeAdapterCapacity(rec *edgenode.NodeRecord, adapterType string) int {
243     switch adapterType {
244     case "ollama":
```

```go
// apps/edge/internal/service/run_dispatch.go:343
343 func nodeSupportsAdapter(rec *edgenode.NodeRecord, adapterType, target string) bool {
347     switch adapterType {
348     case "cli":
```

Node router는 exact instance key를 먼저 조회하고, type-name lookup은 같은 type 인스턴스가 여러 개면 ambiguous error를 낸다.

```go
// apps/node/internal/adapters/registry.go:60
60 func (r *Registry) Lookup(name string) (runtime.Adapter, error) {
61     if e, ok := r.entries[name]; ok {
64     var matchKeys []string
76     return nil, fmt.Errorf("adapter %q is ambiguous: matches instance keys %v; use an instance key", name, matchKeys)
```

#### 해결 방법

Provider config resolver를 하나로 모은다. `req.Adapter`가 provider instance `Name`과 exact-match하면 그 instance만 사용한다. `req.Adapter`가 provider type(`ollama`, `vllm`, `openai_compat`)이면 enabled instance가 정확히 하나일 때만 그 instance를 사용하고, 여러 개면 runnable 후보가 아니거나 explicit ambiguity error를 반환한다. resolved instance의 `Capacity`, `MaxQueue`, `QueueTimeoutMS`만 candidate capacity/group policy에 반영한다.

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

- [ ] `apps/edge/internal/service/run_dispatch.go`: provider instance resolver helper 추가 또는 기존 helper 통합.
- [ ] `apps/edge/internal/service/run_dispatch.go`: `nodeAdapterCapacity`, `adapterPolicyFields`, `nodeSupportsAdapter`가 resolved provider instance를 공유하도록 변경.
- [ ] `apps/edge/internal/service/service_test.go`: instance-key route와 ambiguous type route 테스트 추가.

#### 테스트 작성

- 작성: `TestSubmitRunModelQueueUsesProviderInstancePolicy`.
- 목표: `Adapter: "ollama-local"` 요청이 `ollama-local` instance의 capacity/policy를 쓰고 다른 provider instance policy를 섞지 않는다.
- 작성: `TestSubmitRunModelQueueRejectsAmbiguousProviderType`.
- 목표: 한 node에 enabled `ollama_instances`가 2개 있을 때 `Adapter: "ollama"` queued request는 Node router ambiguous failure로 내려가지 않고 service 단계에서 reject된다.

#### 중간 검증

```bash
go test -count=1 ./apps/edge/internal/service -run 'TestSubmitRunModelQueue(UsesProviderInstancePolicy|RejectsAmbiguousProviderType)'
```

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/edge/internal/service/run_dispatch.go` | REVIEW_REVIEW_REFACTOR-1 |
| `apps/edge/internal/service/service_test.go` | REVIEW_REVIEW_REFACTOR-1 |

## 최종 검증

```bash
go test -count=1 ./apps/edge/internal/service
go test -race -count=1 ./apps/edge/internal/service
go test -count=1 ./apps/edge/...
```

기대 결과: 모두 통과. Go test cache는 이 plan에서 허용하지 않는다.

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