<!-- task=m-edge-model-group-queue-scheduling/02+01_surface_snapshot_contract plan=1 tag=REVIEW_REFACTOR -->

# Plan - REVIEW_REFACTOR Surface Snapshot Contract Follow-up

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

`code_review_cloud_G06_0.log`의 Required 이슈만 해결한다. 구현 후 검증 명령을 실행하고 실제 stdout/stderr, 구현 메모, 계획 대비 변경 사항을 `CODE_REVIEW-cloud-G06.md`에 채운 뒤 active 파일을 남긴 채 리뷰 준비 상태로 보고한다. 최종 판정, log rename, `complete.log`, archive 이동은 code-review-skill 전용이다.

구현 중 사용자만 결정할 수 있는 범위 변경, 외부 환경/secret, 또는 scope conflict가 생기면 chat에서 묻지 말고 review stub의 `사용자 리뷰 요청` 섹션에 근거와 재개 조건을 남긴다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백은 사용자 리뷰 요청 사유가 아니다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/edge-model-group-queue-scheduling.md`
- Task ids:
  - `snapshot-contract`: Edge-visible capacity/in-flight/queued snapshot이 Edge-owned model group 상태를 기준으로 표현되고 Node capability snapshot과 충돌하지 않는다.
  - `surface-coverage`: OpenAI Chat Completions/Responses 실행 경로가 `model` 값 기반 Edge-owned queue를 우회하지 않고, A2A/console 경로는 별도 model group key 없이 같은 Edge dispatch primitive와 충돌하지 않는다.
- Completion mode: check-on-pass

## 배경

첫 리뷰는 FAIL이다. 현재 구현은 기본 happy path 테스트는 통과하지만, queue admission과 snapshot merge가 같은 provider instance identity를 공유하지 않는 케이스가 있다. 또한 console zero-key 검증은 실제 `SendRun()` 경로를 실행하지 않아 계획상 필수 테스트로 보기 어렵다.

## 이전 리뷰 근거

- Archived plan: `agent-task/m-edge-model-group-queue-scheduling/02+01_surface_snapshot_contract/plan_cloud_G06_0.log`
- Archived review: `agent-task/m-edge-model-group-queue-scheduling/02+01_surface_snapshot_contract/code_review_cloud_G06_0.log`
- Verdict: FAIL

## 구현 체크리스트

- [ ] Snapshot stats resolution이 admission에서 허용한 type-name queue group을 같은 단일 enabled named provider instance로 매핑하고, 최소 Ollama single named type-route 회귀 테스트를 추가한다.
- [ ] Console `SendRun` 테스트가 실제 `SendRun` 경로를 실행해 non-empty `ModelGroupKey`/queue path를 사용하지 않는다는 계약을 검증하도록 보강한다.
- [ ] `go test -count=1 ./apps/edge/internal/openai ./apps/edge/internal/input/a2a ./apps/edge/internal/opsconsole ./apps/edge/internal/controlplane ./apps/edge/internal/service`가 통과한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_REFACTOR-1] Snapshot Type-Route Instance Mapping

#### 문제

`apps/edge/internal/service/run_dispatch.go`의 admission 경로는 `adapter="ollama"` 같은 type-name route를 단일 enabled named instance에도 허용한다. 반면 `apps/edge/internal/service/model_queue.go`의 snapshot stats 매칭은 `group.adapter == "ollama"`일 때 empty/default-name instance만 연결한다. 따라서 단일 named instance가 `ollama-local`인 node에서 type-name route로 큐잉된 in-flight/queued 값이 provider snapshot에 반영되지 않는다.

#### 해결 방법

Admission과 snapshot이 같은 canonical provider instance identity를 쓰도록 정리한다. 예를 들어 `resolveAdapterForNode()`가 canonical adapter instance name을 함께 반환하게 하거나, snapshot 전용 helper가 exact-name route와 type-name single-instance route를 같은 규칙으로 canonical name에 매핑하게 한다.

Before:

```go
if inst.Enabled && (inst.Name == group.adapter || (group.adapter == "ollama" && inst.Name == "")) {
```

After:

```go
// type-name route with exactly one enabled named instance maps to that instance name.
canonical, ok := resolveSnapshotAdapterName(rec, group.adapter, group.target)
```

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

- [ ] `apps/edge/internal/service/model_queue.go`: snapshot stats matching을 admission resolution과 일치시킨다.
- [ ] `apps/edge/internal/service/status_provider_test.go`: `adapter="ollama"` group + 단일 enabled `Name:"ollama-local"` instance에서 `ProviderSnapshot{Adapter:"ollama-local"}`의 `InFlight`/`Queued`가 queue state를 반영하는 테스트를 추가한다.
- [ ] 필요 시 `apps/edge/internal/service/run_dispatch.go`의 resolver DTO를 확장하되, 기존 admission semantics는 바꾸지 않는다.

#### 중간 검증

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

### [REVIEW_REFACTOR-2] Console Zero-Key Path Test

#### 문제

`apps/edge/internal/opsconsole/console_test.go`의 새 assertion은 로컬 `SubmitRunRequest` zero value만 확인한다. 실제 `opsconsole.SendRun()`이 service에 넘기는 요청을 관찰하지 않으므로, console 경로가 나중에 non-empty `ModelGroupKey`를 넣거나 queue path를 타도 이 테스트는 실패하지 않는다.

#### 해결 방법

실제 `SendRun()`을 실행하는 테스트로 바꾼다. 가능한 방식은 real `edgeservice.Service` + fake node client를 사용하고, node store를 `ModelGroupKey`가 non-empty일 경우 queue admission이 실패하는 설정으로 둔 뒤 `SendRun()`이 성공하는지 확인하는 것이다. 이 테스트는 console이 zero key direct SubmitRun path를 유지한다는 회귀 가드가 된다.

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

- [ ] `apps/edge/internal/opsconsole/console_test.go`: 기존 zero-value assertion을 실제 `SendRun()` 실행 기반 테스트로 대체하거나 보강한다.
- [ ] 테스트가 console 사용자 출력 형식을 불필요하게 바꾸지 않도록 한다.

#### 중간 검증

```bash
go test -count=1 ./apps/edge/internal/opsconsole -run TestSendRun_SubmitRunRequest_MetadataSource
```

## 최종 검증

```bash
go test -count=1 ./apps/edge/internal/openai ./apps/edge/internal/input/a2a ./apps/edge/internal/opsconsole ./apps/edge/internal/controlplane ./apps/edge/internal/service
```

기대 결과: 통과. Go test cache는 허용하지 않는다.
