<!-- task=m-model-route-queue-policy-alignment/02+01_service_queue_policy plan=1 tag=REVIEW_API -->

# Plan - REVIEW_API service queue policy test determinism

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

이 계획은 직전 코드리뷰 WARN의 단일 Suggested 이슈만 처리한다. 구현 완료 후 `CODE_REVIEW-local-G07.md`의 구현 에이전트 소유 섹션을 채우는 것이 필수 최종 단계다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 충돌로 막히면 review stub의 `사용자 리뷰 요청`에 정확한 근거를 남기고 멈춘다. 구현 에이전트는 사용자에게 직접 묻거나 `USER_REVIEW.md`, `complete.log`, archive 로그를 만들지 않는다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/model-route-queue-policy-alignment.md`
- Task ids:
  - `queue-policy`: Edge queue admission이 route-level queue policy를 adapter instance fallback보다 우선하고, route policy가 없을 때 기존 fallback을 유지한다.
- Completion mode: check-on-pass

## Spec Targets

- SDD: `agent-roadmap/sdd/inference-provider-extension/model-route-queue-policy-alignment/SDD.md`
- Acceptance scenarios:
  - `S03`: task=`queue-policy`; evidence=`go test -count=1 ./apps/edge/internal/service`
- Completion mode: spec-check-on-pass

## Archive Evidence Snapshot

- Current archived plan: `agent-task/m-model-route-queue-policy-alignment/02+01_service_queue_policy/plan_local_G07_0.log`
- Current archived review: `agent-task/m-model-route-queue-policy-alignment/02+01_service_queue_policy/code_review_local_G07_0.log`
- Prior verdict: WARN
- Required issues: 없음
- Suggested issues:
  - `apps/edge/internal/service/service_test.go:1571`에서 `time.Sleep(50 * time.Millisecond)`로 run2 큐 진입을 추정하고, `apps/edge/internal/service/service_test.go:1575`에서 `context.Background()`로 run3을 호출해 스케줄링 지연 시 30초 queue timeout 경로로 빠질 수 있다. `errCh2`도 회수하지 않아 goroutine cleanup 신뢰성이 낮다.
- Nit:
  - `apps/edge/internal/service/run_dispatch.go:187` stale comment는 리뷰 중 정리됨.
- Affected files:
  - `apps/edge/internal/service/service_test.go`
  - `apps/edge/internal/service/run_dispatch.go` (comment-only cleanup already applied)
- Verification evidence from prior loop:
  - `go test -count=1 ./apps/edge/internal/service` -> PASS (`ok iop/apps/edge/internal/service 0.401s`)
  - `git diff --check` -> PASS (no output)
  - `go test -count=50 -run TestSubmitRunModelQueueUsesRoutePolicyBeforeProviderInstancePolicy ./apps/edge/internal/service` -> PASS (`ok iop/apps/edge/internal/service 2.597s`)
- Roadmap/spec carryover:
  - Roadmap task `queue-policy` remains the target.
  - SDD scenario `S03` remains the target.
- Allowed archive reread:
  - `agent-task/m-model-route-queue-policy-alignment/02+01_service_queue_policy/plan_local_G07_0.log`
  - `agent-task/m-model-route-queue-policy-alignment/02+01_service_queue_policy/code_review_local_G07_0.log`

## 범위 결정 근거

- 포함: `TestSubmitRunModelQueueUsesRoutePolicyBeforeProviderInstancePolicy`의 큐 진입 대기와 goroutine cleanup을 deterministic하게 만든다.
- 제외: `groupPolicyFromRequestOrStore` 구현 로직, OpenAI dispatch, config schema, SDD/roadmap 문서 변경.
- 빌드 등급: `local-G07`. 동시성 테스트 신뢰성 문제지만 focused service test와 반복 targeted test로 로컬에서 판정 가능하다.

## 구현 체크리스트

- [ ] `TestSubmitRunModelQueueUsesRoutePolicyBeforeProviderInstancePolicy`에서 run2가 큐에 들어갔음을 `time.Sleep`만으로 추정하지 않도록 bounded retry 또는 명시적 동기화 구조로 바꾼다.
- [ ] run3 queue-full 검증은 `context.Background()` 무제한 대기 대신 짧은 timeout/retry boundary를 사용해 실패 시에도 테스트가 길게 멈추지 않게 한다.
- [ ] `cancel2()` 이후 `errCh2`를 읽어 run2 goroutine 종료를 확인하고, 기대 가능한 error를 검증한다.
- [ ] Targeted 반복 테스트, focused service tests, `git diff --check`를 실행한다.
- [ ] `CODE_REVIEW-local-G07.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_API-1] Route policy queue-full test determinism

#### 문제

직전 리뷰에서 `apps/edge/internal/service/service_test.go:1571`의 `time.Sleep(50 * time.Millisecond)`와 `apps/edge/internal/service/service_test.go:1575`의 unbounded `SubmitRun(context.Background(), ...)` 조합이 검증 신뢰성을 낮춘다고 판단했다. run2 goroutine이 아직 큐에 들어가지 않은 상태에서 run3이 먼저 큐에 들어가면 queue-full이 아니라 queue-timeout/cancel 경로를 검증하게 된다.

#### 해결 방법

- run2를 cancelable context로 시작한 뒤, run3 queue-full 관찰은 짧은 timeout을 둔 probe/retry로 제한한다.
- retry loop는 queue-full error가 관찰되면 성공하고, 전체 deadline 안에 관찰하지 못하면 테스트를 실패시킨다.
- 각 probe가 queue-full이 아니라 context timeout으로 끝난 경우에는 queue item이 제거된 뒤 다시 시도하도록 한다.
- 검증 후 `cancel2()`를 호출하고 `errCh2`를 bounded select로 읽어 goroutine이 종료됐는지 확인한다.

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

- [ ] `apps/edge/internal/service/service_test.go`: sleep-only 대기를 제거하거나 보조적인 짧은 scheduling hint로 축소하고, queue-full 관찰에 bounded retry를 적용한다.
- [ ] `apps/edge/internal/service/service_test.go`: run2 goroutine cleanup 검증을 추가한다.

#### 테스트 작성

기존 테스트를 갱신한다. 새 테스트 파일은 만들지 않는다.

#### 중간 검증

```bash
go test -count=20 -run TestSubmitRunModelQueueUsesRoutePolicyBeforeProviderInstancePolicy ./apps/edge/internal/service
```

기대 결과: targeted repeated test PASS. Go test cache 출력은 허용하지 않는다.

## 최종 검증

```bash
go test -count=1 ./apps/edge/internal/service
git diff --check
```

기대 결과: 모든 명령 PASS. Go test cache 출력은 허용하지 않는다.

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