<!-- task=m-model-group-long-context-admission/02+01_long_capacity plan=0 tag=LONG_CAPACITY -->

# Code Review Reference - LONG_CAPACITY

> **[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 run review finalization. Do not ask the user directly during implementation.

## 개요

date=2026-07-05
task=m-model-group-long-context-admission/02+01_long_capacity, plan=0, tag=LONG_CAPACITY

## Roadmap Targets

- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/model-group-long-context-admission.md`
- Task ids:
  - `provider-long-capacity`: provider별 total context budget, long capacity, long_in_flight 상태와 동시 점유
  - `provider-exclusion`: long slot이 찬 provider를 long 후보에서 제외하고 normal 후보는 유지
- Completion mode: check-on-pass

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

> **[REVIEW AGENT ONLY]** 종결 절차는 코드리뷰 에이전트 전용이다. PASS이면 완료 이벤트만 보고하고 roadmap을 직접 수정하지 않는다.

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [LONG_CAPACITY-1] Provider long capacity config와 validation | [x] |
| [LONG_CAPACITY-2] Long slot reserve/release와 provider exclusion | [x] |

## 구현 체크리스트

- [x] provider config에 `total_context_tokens`, `long_context_capacity`를 추가하고 `total_context_tokens >= context_window_tokens * long_context_capacity`를 config load/check에서 검증한다.
- [x] config refresh classifier가 provider long capacity 관련 field를 live-apply로 분류하게 한다.
- [x] long 요청 dispatch 시 일반 capacity와 long capacity를 함께 reserve/release하고 `long_in_flight` 상태를 유지한다. 검증: long dispatch 중 두 counter가 증가하고 완료/오류/취소 후 회복된다.
- [x] long slot이 full인 provider는 long 후보에서 제외하되, normal 요청 후보에서는 일반 capacity만 기준으로 유지한다. 검증: long은 다른 provider 또는 queue 대기, normal은 남은 일반 slot으로 dispatch된다.
- [x] config/service/configrefresh regression tests와 config check를 실행한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 구현 요약

- `packages/go/config/config.go`: `NodeProviderConf`에 `TotalContextTokens`, `LongContextCapacity` 추가. provider-level `Validate()`에 non-negative 검증과 `long_context_capacity > 0 → total_context_tokens > 0` 불변식 추가. `LoadEdge`에서 `providerByID` 맵을 만들고 `validateProviderLongContextBudget`로 `context_window_tokens > 0`인 model group이 참조하는 provider마다 `total_context_tokens >= context_window_tokens * long_context_capacity`를 검증.
- `configs/edge.yaml`: 두 field 주석 문서화, `mac-mlx-vllm` provider에 `total_context_tokens: 524288`, `long_context_capacity: 2` 예시 추가(262144*2 만족).
- `apps/edge/internal/configrefresh/classify.go`: `providerKey`에 두 field 추가, `buildProviderIndex`에서 채우고, capacity/priority와 동일하게 `StatusApplied`(live-apply)로 diff.
- `apps/edge/internal/service/model_queue.go`: `candidateNode.longContextCapacity`, `modelQueueGroup.longInflight`, `queueItem.long`, `inflightRec.long` 추가. `admit`/`trackInflight`에 variadic long flag(파일 기존 `releaseSlot` variadic 관례 준수), `findAvailableNodeLocked`/`releaseSlotLocked`에 명시적 long 파라미터. long 요청은 일반 `inflight`와 `longInflight`를 함께 예약/해제하고, long slot full provider는 long 후보에서만 제외. `longInFlightForProvider` 조회 helper 추가. 미사용 `releaseSlotByCandidate` 제거.
- `apps/edge/internal/service/run_dispatch.go`: `contextClassLong` 상수, provider-pool candidate에 `longContextCapacity` 전달, `submitRunQueued`에서 `ContextClass == "long"`를 admit/track/release 경로에 연결.

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

- [x] 판정을 append한다.
- [x] active review/plan을 `code_review_local_G08_0.log`, `plan_local_G08_0.log`로 아카이브한다.
- [x] PASS이면 `complete.log` 작성 후 active task 디렉터리를 archive로 이동한다.
- [x] PASS split 작업이면 빈 active parent 처리 여부를 확인한다. active parent에는 sibling subtask가 남아 있어 유지했다.
- [ ] WARN/FAIL이면 다음 active plan/review 또는 `USER_REVIEW.md`를 작성한다.

## 계획 대비 변경 사항

- 계획의 gate 식(`req.ContextClass == "long" && cand.longContextCapacity > 0 && cand.longInFlight >= cand.longContextCapacity → continue`)은 그대로 `findAvailableNodeLocked`에 적용했다. 다만 상태를 후보 struct 필드가 아니라 `modelQueueGroup.longInflight` 카운터로 보관해 기존 `inflight` 관리 방식과 대칭을 맞췄다.
- `admit`/`trackInflight`의 long flag는 별도 파라미터를 추가하되 기존 테스트/호출부 20여 곳을 깨지 않도록 파일에 이미 존재하는 `releaseSlot(... providerID ...string)` variadic 관례를 따라 variadic으로 넣었다. 시그니처가 내부 전용인 `findAvailableNodeLocked`/`releaseSlotLocked`는 명시적 파라미터로 바꾸고 해당 테스트 호출부만 갱신했다.
- 계획에 없던 정리: 호출부가 없는 `releaseSlotByCandidate`를 제거했다(grep 결과 주석 1개 외 참조 없음). 범위 내 비동작성 정리로 판단.

## 주요 설계 결정

- `long_context_capacity == 0` provider는 long 요청 후보에서 제외하지 않고 long slot도 예약하지 않는다(계획 gate의 `> 0` 조건 그대로). 즉 long slot 카운터는 항상 capacity 이내에서만 의미를 가진다. long을 서빙할 provider는 SDD 표준선대로 `long_context_capacity > 0`을 설정한다.
- long in-flight 조회 helper(`longInFlightForProvider`)는 `group.longInflight` 예약 카운터가 아니라 기존 `getStatsForProviderLocked`와 동일하게 `inflightByRun`의 `long` flag를 세어 도출한다. proto `ProviderSnapshot` 노출은 04 범위이므로 service-local 상태/조회에 한정했다.
- budget 검증은 `context_window_tokens > 0`인 model group이 참조하는 provider에만 적용한다. `long_context_capacity == 0`이면 필요 budget이 0이라 자연히 통과하므로 무조건 비교로 단순화했다.
- config refresh 분류는 계획대로 두 field 모두 `StatusApplied`(live-apply)로 두어 capacity/priority와 일관되게 했다.

## 사용자 리뷰 요청

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

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

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

- `total_context_tokens >= context_window_tokens * long_context_capacity` 검증이 model provider mapping 기준으로 동작하는지 확인한다.
- long request가 일반 capacity와 long capacity를 함께 점유하고 terminal path에서 둘 다 회복하는지 확인한다.
- long slot full provider가 normal 후보에서 제외되지 않는지 확인한다.

## 검증 결과

### LONG_CAPACITY-1 중간 검증

```bash
$ go test -count=1 ./packages/go/config ./apps/edge/internal/configrefresh
ok  	iop/packages/go/config	0.034s
ok  	iop/apps/edge/internal/configrefresh	0.015s
```

새 테스트: `TestLoadEdgeProviderLongContextCapacityValidation`(valid + negative total/long + long-without-total), `TestLoadEdgeRejectsProviderLongContextBudgetBelowModelWindow`, `TestClassifyProviderLongContextCapacityApplied`.

### LONG_CAPACITY-2 중간 검증

```bash
$ go test -count=1 ./apps/edge/internal/service
ok  	iop/apps/edge/internal/service	0.463s
```

새 테스트: `TestModelQueueLongDispatchReservesBothCounters`(complete/error/cancelled 3 subtest), `TestModelQueueNormalRequestDoesNotReserveLongSlot`, `TestModelQueueLongSlotFullExcludesLongKeepsNormal`, `TestRefreshProviderLongCapacityAffectsCandidate`.

### 최종 검증

```bash
$ go test -count=1 ./packages/go/config ./apps/edge/internal/configrefresh ./apps/edge/internal/edgevalidate ./apps/edge/internal/service
ok  	iop/packages/go/config	0.036s
ok  	iop/apps/edge/internal/configrefresh	0.022s
ok  	iop/apps/edge/internal/edgevalidate	0.006s
ok  	iop/apps/edge/internal/service	0.463s
$ go run ./apps/edge/cmd/edge config check --config configs/edge.yaml
OK configs/edge.yaml
```

보조 확인: `go build ./...` 통과, `gofmt -l`(변경 파일) 출력 없음, `git diff --check` whitespace 오류 없음.

---

> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, user review request state, and verification output?**

## 코드리뷰 결과

- 종합 판정: PASS
- 차원별 평가:
  - correctness: Pass
  - completeness: Pass
  - test coverage: Pass
  - API contract: Pass
  - code quality: Pass
  - implementation deviation: Pass
  - verification trust: Pass
  - spec conformance: Pass
- 발견된 문제: 없음
- 다음 단계: PASS 종결 절차를 수행한다. active review/plan을 로그로 아카이브하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다.

검토 메모:
- `total_context_tokens`/`long_context_capacity` schema, validation, refresh classification, provider-pool candidate propagation이 plan과 `iop.edge-config-runtime-refresh` 계약에 맞게 반영됐다.
- long 요청은 일반 `inflight`와 `longInflight`를 함께 reserve/release하고, terminal `complete`/`error`/`cancelled` 경로에서 둘 다 회복된다.
- long slot full provider는 long 후보에서 제외되지만 normal 요청 후보에서는 유지된다. status/proto 노출은 `04+01,02_status_logs` 범위로 분리되어 있어 이번 task의 service-local evidence 범위와 충돌하지 않는다.
- 재검증:
  - `go test -count=1 ./packages/go/config ./apps/edge/internal/configrefresh ./apps/edge/internal/edgevalidate ./apps/edge/internal/service` PASS
  - `go run ./apps/edge/cmd/edge config check --config configs/edge.yaml` PASS, `OK configs/edge.yaml`
  - `gofmt -l` on changed Go files PASS, no output
  - `git diff --check` PASS, no output
  - `go build ./...` PASS
