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

# Plan - API service queue policy priority

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

구현 완료 후 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채우는 것이 필수 최종 단계입니다. 이 task는 `01_config_route_policy` 완료 후 진행해야 합니다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 충돌로 막히면 review stub의 `사용자 리뷰 요청`에 정확한 근거를 남기고 멈춥니다. 구현 에이전트는 사용자에게 직접 묻거나 `USER_REVIEW.md`, `complete.log`, archive 로그를 만들지 않습니다.

## 배경

선행 task가 route-level queue policy를 `SubmitRunRequest`에 실어 보낸다. 이 후속 작업은 Edge service가 그 값을 실제 model queue group policy로 우선 적용하게 만든다. route policy가 없으면 기존 Node/provider adapter instance fallback을 유지해야 기존 설정을 깨지 않는다.

## 사용자 리뷰 요청 흐름

구현 중 차단은 active review stub의 `사용자 리뷰 요청` 섹션에 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식으로 기록한다. 직접 사용자 프롬프트는 금지되며, code-review가 USER_REVIEW 작성 여부를 판단한다.

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

## 분석 결과

### 읽은 파일

- `agent-roadmap/current.md`
- `agent-roadmap/phase/inference-provider-extension/milestones/model-route-queue-policy-alignment.md`
- `agent-roadmap/sdd/inference-provider-extension/model-route-queue-policy-alignment/SDD.md`
- `agent-test/local/rules.md`
- `agent-test/local/edge-smoke.md`
- `agent-ops/rules/project/domain/edge/rules.md`
- `apps/edge/internal/service/run_dispatch.go`
- `apps/edge/internal/service/model_queue.go`
- `apps/edge/internal/service/service_test.go`

### 테스트 환경 규칙

- test_env: local.
- `agent-test/local/rules.md` present/read.
- Matched profile read: `agent-test/local/edge-smoke.md`.
- 적용 명령: `go test -count=1 ./apps/edge/internal/service`, `git diff --check`.
- Edge service dispatch 변경이므로 focused service tests가 필수다. 전체 edge smoke는 queue policy 자체보다 실행 경로 생존 확인 보조로 후속 review가 필요하다고 판단할 때만 확장한다.

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

- `resolveQueueCandidates`는 group policy가 adapter config에서 온다는 전제를 갖고 있다.
- `TestSubmitRunModelQueueUsesProviderInstancePolicy`는 adapter instance `MaxQueue` fallback만 검증한다.
- route-level policy가 adapter fallback보다 우선하는 regression test가 없다.

### 심볼 참조

- 제거/rename 없음.
- 선행 task가 추가할 `SubmitRunRequest.MaxQueue`, `SubmitRunRequest.QueueTimeoutMS`를 소비한다.
- call site: `submitRunQueued`, `resolveQueueCandidates`, `groupPolicyFromStore`, service tests.

### 분할 판단

- split decision policy evaluated. 이 plan은 `02+01_service_queue_policy`이며 predecessor `01_config_route_policy`의 `complete.log`가 필요하다.
- 현재 작성 시점에는 predecessor active task가 생성되었지만 완료되지 않았다. 구현 runtime은 `01_config_route_policy` 완료 후 이 task를 실행해야 한다.

### 범위 결정 근거

- 포함: Edge service queue policy derivation and tests.
- 제외: config schema/OpenAI handler/docs 변경은 `01_config_route_policy` 담당.
- 제외: Node/provider admission behavior, GPU metric detection, cross-Edge routing.

### 빌드 등급

- build lane/grade: `local-G07`. Concurrency/queue policy path지만 focused service tests로 deterministic하게 검증 가능하다.

## 구현 체크리스트

- [ ] `SubmitRunRequest`의 route-level queue policy가 있으면 Edge group policy에서 adapter fallback보다 우선 사용한다.
- [ ] route policy가 0이면 기존 adapter instance `max_queue`/`queue_timeout_ms` fallback과 default policy가 유지된다.
- [ ] route policy override와 fallback regression tests를 추가/갱신한다.
- [ ] Focused service tests와 `git diff --check`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [API-1] Route policy 우선순위 적용

#### 문제

`apps/edge/internal/service/run_dispatch.go:187`의 comment와 구현은 group policy를 adapter config에서만 파생한다. `groupPolicyFromStore`는 `run_dispatch.go:397`에서 후보 Node adapter instance의 `maxQueue`, `queueTimeoutMS`를 먼저 만나면 반환한다.

```go
policy := groupPolicyFromStore(s.nodeStore, entries, req.Adapter, req.Target)
```

#### 해결 방법

`SubmitRunRequest`의 route-level policy를 먼저 `groupPolicy`로 변환하는 helper를 추가한다.

```go
func groupPolicyFromRequestOrStore(req SubmitRunRequest, store *edgenode.NodeStore, entries []*edgenode.NodeEntry) groupPolicy {
    if req.MaxQueue > 0 || req.QueueTimeoutMS > 0 {
        return groupPolicy{
            maxQueue:     positiveOr(req.MaxQueue, defaultGroupMaxQueue),
            queueTimeout: queueTimeoutOrDefault(req.QueueTimeoutMS),
        }
    }
    return groupPolicyFromStore(store, entries, req.Adapter, req.Target)
}
```

`resolveQueueCandidates`의 explicit-node path와 all-candidates path 모두 이 helper를 사용한다. 음수는 config validation에서 막지만 service direct caller 방어를 위해 `<=0`은 fallback/default로만 해석한다.

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

- [ ] `apps/edge/internal/service/run_dispatch.go`: route policy 우선 helper 추가.
- [ ] `apps/edge/internal/service/run_dispatch.go`: explicit NodeRef path와 all-candidates path 모두 helper 사용.

#### 테스트 작성

작성.
- `TestSubmitRunModelQueueUsesRoutePolicyBeforeProviderInstancePolicy`: provider instance `MaxQueue`가 큰 값이어도 request `MaxQueue: 1`이면 두 번째 queued request 후 세 번째 대기 request가 queue full로 거부되는지 검증한다.
- `TestSubmitRunModelQueueFallsBackToProviderInstancePolicy`: request policy가 0이면 기존 `TestSubmitRunModelQueueUsesProviderInstancePolicy` 기대가 유지되는지 검증한다. 기존 test를 이름 유지한 채 보강하거나 새 test로 분리한다.

#### 중간 검증

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

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

### [API-2] Queue timeout route policy 검증

#### 문제

`model_queue.go:183`은 `group.policy.queueTimeout`으로 queued item deadline을 만든다. route-level `queue_timeout_ms`가 적용되지 않으면 model alias queue의 timeout 정책을 독립적으로 설정할 수 없다.

#### 해결 방법

API-1의 helper에서 `QueueTimeoutMS`가 있으면 `time.Duration(req.QueueTimeoutMS) * time.Millisecond`를 우선 사용한다. `MaxQueue`만 있고 timeout이 없으면 default timeout을 사용한다. `QueueTimeoutMS`만 있고 max queue가 없으면 default max queue를 사용한다.

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

- [ ] `apps/edge/internal/service/run_dispatch.go`: timeout conversion helper 작성.
- [ ] `apps/edge/internal/service/service_test.go`: timeout override test 추가 또는 max_queue override test 안에 assertion 포함.

#### 테스트 작성

작성.
- `TestSubmitRunModelQueueUsesRouteQueueTimeout`: capacity를 채우고 queued request context보다 route timeout을 짧게 설정해 `errQueueTimeout` path가 route timeout 기준으로 발생하는지 검증한다.

#### 중간 검증

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

기대 결과: model queue 관련 service tests PASS. Go test cache 출력은 허용하지 않는다.

## 수정 파일 요약

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

## 의존 관계 및 구현 순서

이 task directory name `02+01_service_queue_policy`가 runtime dependency source of truth다.
Predecessor `01_config_route_policy` must produce `agent-task/m-model-route-queue-policy-alignment/01_config_route_policy/complete.log` or matching archived complete log before implementation starts.

## 최종 검증

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

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

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