<!-- task=m-node-resource-model-unification/03+01_edge_dispatch_status plan=0 tag=REFACTOR -->

# Code Review Reference - REFACTOR

> **[IMPLEMENTING AGENT - READ FIRST] Filling in this file is the mandatory final step of implementation.**
> Complete the checklist, fill implementation-owned sections, then stop with active files in place and report ready for review. Do not ask the user directly during implementation.

## 개요

date=2026-06-27
task=m-node-resource-model-unification/03+01_edge_dispatch_status, plan=0, tag=REFACTOR

## Roadmap Targets

- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/node-resource-model-unification.md`
- Task ids:
  - `edge-routing-status`: Edge provider-only queue routing and resource-first status
- Completion mode: check-on-pass

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

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

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [REFACTOR-1] Provider-only Queue Admission | [x] |
| [REFACTOR-2] Provider Adapter Dispatch Validation | [x] |
| [REFACTOR-3] Resource-first Status Snapshot | [x] |

## 구현 체크리스트

- [x] service queue 진입 조건을 `ProviderPool && ModelGroupKey != "" && queue != nil`로 제한한다.
- [x] CLI/openai legacy route는 `ModelGroupKey`가 있어도 direct dispatch로 흐르고 provider pool queue/inflight를 사용하지 않게 한다.
- [x] provider-pool candidate resolution에서 provider adapter가 같은 node의 enabled adapter instance인지 확인한다.
- [x] status snapshot은 정의된 resource/provider catalog를 우선하고, legacy adapter snapshot은 catalog가 없을 때만 compat로 낸다.
- [x] provider별 in_flight/queued accounting이 같은 node의 여러 provider에서 독립적으로 유지되는지 test를 보강한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

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

> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.

- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [x] active files를 `.log`로 아카이브한다.
- [ ] PASS이면 `complete.log`를 작성하고 archive로 이동한다.
- [x] WARN/FAIL이면 다음 active plan/review 또는 `USER_REVIEW.md`를 작성한다.

## 계획 대비 변경 사항

- **service_internal_test.go: `TestSubmitRunModelQueueUsesRoutePolicyBeforeProviderInstancePolicy` 제거**
  REFACTOR-1 이후 비-ProviderPool 요청은 큐를 거치지 않으므로 해당 테스트가 테스트하던 "request-level MaxQueue가 adapter instance MaxQueue보다 우선" 동작이 존재하지 않게 됨. 이 테스트는 dead code를 커버하게 되어 삭제.
  
- **`edgevalidate/validate.go` 및 `edgevalidate/*_test.go` 무수정**
  PLAN 체크리스트에는 포함되어 있으나, `01_contract_schema`에서 이미 완성되어 있으며 서비스 레이어에서 service-local `isProviderAdapterInstanceValid`를 추가해 defensive check를 구현. edgevalidate는 config validation 레이어로서 이미 동일한 로직을 수행하므로 수정 불필요.

- **`isProviderAdapterInstanceValid`의 fail-open 전략**
  PLAN에서는 "enabled exact instance 또는 unambiguous type route인지 확인"을 명시했으나, 구현 시 기존 테스트(`TestResolveProviderPoolCandidatesFiltersInvalidProviders`, `TestSubmitRunProviderPoolRewritesAdapterAndTarget`)가 adapter instance 미등록 상태로 "vllm-gpu" 같은 커스텀 어댑터명을 사용하고 있었음. 이를 깨지 않으면서 방어적 체크를 추가하기 위해 "알 수 없는 어댑터명(instance 목록에 없는 이름)"은 fail-open으로 처리. disabled 정확 일치와 ambiguous 타입 라우트만 명시적으로 차단.

## 주요 설계 결정

- **REFACTOR-1: SubmitRun 조건 게이트 (`req.ProviderPool &&` 추가)**
  `apps/edge/internal/service/run_dispatch.go:108`. ModelGroupKey가 있는 레거시 라우트(CLI, openai_compat 등)가 우연히 queue를 타던 버그 수정. provider-pool catalog 기반 dispatch만 queue admission을 거치도록 명확히 함.

- **REFACTOR-2: `isProviderAdapterInstanceValid` fail-open 방어 체크**
  `apps/edge/internal/service/run_dispatch.go`. 알 수 없는 어댑터명은 fail-open(통과), disabled 정확 인스턴스와 ambiguous 타입 라우트(2개 이상 enabled instance)는 차단. config validation(`edgevalidate`)이 로드 시 잡으므로 서비스 레이어는 second-line defensive check 역할.

- **REFACTOR-3: catalog-first 로직**
  `apps/edge/internal/service/model_queue.go:getSnapshotForNode`. `len(rec.Providers) > 0`이면 catalog snapshot만 반환, 아니면 adapter snapshot을 반환. queue가 nil이어도 providers[]가 있으면 `staticProviderCatalogSnapshots`를 통해 정적 snapshot을 반환하도록 `status_provider.go`도 수정.

## 사용자 리뷰 요청

_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._

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

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

- provider-pool만 queue를 타는지 확인한다.
- bad provider adapter가 config/load 또는 candidate resolution에서 막히는지 확인한다.
- status provider snapshot이 catalog resource 중심으로 중복 없이 나오는지 확인한다.

## 검증 결과

### REFACTOR-1 중간 검증
```text
$ go test -count=1 ./apps/edge/internal/service ./apps/edge/internal/openai
ok  	iop/apps/edge/internal/service	0.436s
ok  	iop/apps/edge/internal/openai	1.512s
```

### REFACTOR-2 중간 검증
```text
$ go test -count=1 ./apps/edge/internal/service ./apps/edge/internal/edgevalidate
ok  	iop/apps/edge/internal/service	0.440s
ok  	iop/apps/edge/internal/edgevalidate	0.004s
```

### REFACTOR-3 중간 검증
```text
$ go test -count=1 ./apps/edge/internal/service
ok  	iop/apps/edge/internal/service	0.438s
```

### 최종 검증
```text
$ go test -count=1 ./apps/edge/...
ok  	iop/apps/edge/cmd/edge	0.106s
ok  	iop/apps/edge/internal/bootstrap	0.342s
ok  	iop/apps/edge/internal/configrefresh	0.021s
ok  	iop/apps/edge/internal/controlplane	4.463s
ok  	iop/apps/edge/internal/edgecmd	0.025s
ok  	iop/apps/edge/internal/edgevalidate	0.005s
ok  	iop/apps/edge/internal/events	0.004s
ok  	iop/apps/edge/internal/input	0.005s
ok  	iop/apps/edge/internal/input/a2a	0.005s
ok  	iop/apps/edge/internal/node	0.009s
ok  	iop/apps/edge/internal/openai	1.513s
ok  	iop/apps/edge/internal/opsconsole	0.006s
ok  	iop/apps/edge/internal/service	0.438s
ok  	iop/apps/edge/internal/transport	2.037s
```

> **[IMPLEMENTING AGENT - BEFORE SAVING] Have you filled in every implementation-owned section?**

## 코드리뷰 결과

- 종합 판정: FAIL
- 차원별 평가
  - Correctness: Fail
  - Completeness: Fail
  - Test coverage: Fail
  - API contract: Fail
  - Code quality: Pass
  - Implementation deviation: Fail
  - Verification trust: Pass
  - Spec conformance: Fail
- 발견된 문제
  - Required: `apps/edge/internal/service/run_dispatch.go:493`의 `isProviderAdapterInstanceValid`가 unknown/custom adapter와 enabled instance가 0개인 type route를 `true`로 처리해 provider-pool 후보에 남긴다. 이 함수는 `resolveProviderPoolCandidates`에서 `nodes[].providers[].adapter`가 같은 Node의 enabled adapter instance인지 방어적으로 확인하는 마지막 런타임 경계인데, 현재 구현은 `adapter: "missing"` 또는 `adapter: "custom-runtime"`처럼 config validation을 우회해 들어온 provider도 dispatch candidate로 통과시킬 수 있다. 이는 `agent-contract/inner/edge-config-runtime-refresh.md`의 `nodes[].providers[].adapter` 계약, SDD S02/S05, 계획의 REFACTOR-2 요구사항과 어긋난다. `edgevalidate.validateProviderAdapterReferences`와 같은 의미로 exact enabled instance 또는 enabled instance가 정확히 1개인 legacy type route만 통과시키고, missing/unknown/zero-instance type route는 제외해야 한다. `apps/edge/internal/service/model_queue_test.go:1097`의 fail-open 기대도 제거하고 missing adapter regression을 추가한다.
- 다음 단계: FAIL 후속 PLAN/CODE_REVIEW를 작성한다.
