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

# Plan - LONG_CAPACITY

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

이 task는 `01_input_estimator`의 `complete.log`가 생긴 뒤 시작한다. 구현 완료 전 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채워야 하며, code-review 종결 절차는 실행하지 않는다. Milestone lock 결정이 새로 필요할 때만 review stub의 `사용자 리뷰 요청`에 기록하고 직접 사용자에게 묻지 않는다. 환경/secret/dev runtime 준비 부족은 사용자 리뷰 요청이 아니라 검증 또는 follow-up 사유다.

## 배경

Milestone은 long 요청이 일반 `in_flight`와 별도 `long_in_flight`를 함께 점유하도록 요구한다. 현재 provider pool은 일반 capacity, priority, `in_flight` 중심으로 후보를 고르며 provider별 long slot과 total context budget 검증이 없다. 이 작업은 provider config schema와 service admission 상태를 추가한다.

## 사용자 리뷰 요청 흐름

구현 중 사용자 리뷰 요청은 active review stub의 `사용자 리뷰 요청` 섹션에만 기록한다. 직접 질문, 선택지 제시, input tool 호출은 금지된다. code-review가 `USER_REVIEW.md` 작성을 소유한다.

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

## 분석 결과

### 읽은 파일

- `agent-roadmap/phase/operational-observability-provider-management/milestones/model-group-long-context-admission.md`
- `agent-roadmap/sdd/operational-observability-provider-management/model-group-long-context-admission/SDD.md`
- `agent-test/local/rules.md`
- `agent-test/local/edge-smoke.md`
- `agent-test/local/platform-common-smoke.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-contract/index.md`
- `agent-contract/inner/edge-config-runtime-refresh.md`
- `packages/go/config/config.go`
- `packages/go/config/config_test.go`
- `apps/edge/internal/configrefresh/classify.go`
- `apps/edge/internal/configrefresh/classify_test.go`
- `apps/edge/internal/edgevalidate/validate.go`
- `configs/edge.yaml`
- `apps/edge/internal/service/run_dispatch.go`
- `apps/edge/internal/service/model_queue.go`
- `apps/edge/internal/service/model_queue_test.go` (provider-pool/priority/disabled provider 구간 확인)
- `apps/edge/internal/service/service.go`
- `apps/edge/internal/service/service_test.go`
- `apps/edge/internal/service/service_internal_test.go`
- `apps/edge/internal/service/status_provider.go`
- `apps/edge/internal/service/status_provider_test.go`

### SDD 기준

- SDD: `agent-roadmap/sdd/operational-observability-provider-management/model-group-long-context-admission/SDD.md`, 상태 `[승인됨]`.
- 대상 Acceptance: S04 `provider-long-capacity`, S05 `provider-exclusion`.
- Evidence Map: config validation, scheduler/service test, provider snapshot 또는 dev mixed smoke.
- 적용: config schema/validation을 먼저 추가하고, service queue state의 long slot reserve/release와 candidate exclusion을 같은 review unit에 둔다.

### 테스트 환경 규칙

- test_env: `local`.
- local edge smoke는 Go test와 config check 중심이다.
- 최종 검증은 fresh 실행: `go test -count=1 ./packages/go/config ./apps/edge/internal/configrefresh ./apps/edge/internal/edgevalidate ./apps/edge/internal/service` 및 config check.

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

- provider config numeric validation: 기존 config tests에 capacity/priority 일부는 있으나 `total_context_tokens`, `long_context_capacity` 없음. 새 boundary tests 필요.
- live refresh classification: 새 provider fields 분류 없음. 새 test 필요.
- long slot reserve/release: 기존 queue tests는 일반 `in_flight`와 priority 중심. long-specific reserve/release/exclusion tests 필요.

### 심볼 참조

- renamed/removed symbol: none.
- 새 field 후보: `config.NodeProviderConf.TotalContextTokens`, `LongContextCapacity`; `candidateNode.longContextCapacity`; `inflightRec.contextClass` 또는 long 여부.
- call sites: provider validation in `packages/go/config`, candidate build in `apps/edge/internal/service/run_dispatch.go`, queue reserve/release/snapshot in `apps/edge/internal/service/model_queue.go`, config refresh classification.

### 분할 판단

- shared task group: `agent-task/m-model-group-long-context-admission`.
- 현재 subtask `02+01_long_capacity`는 predecessor index `01`에 의존한다.
- 작성 시점에 `agent-task/m-model-group-long-context-admission/01_input_estimator/complete.log`는 없다. 구현 시작 전 해당 active sibling 또는 archive predecessor `complete.log`가 필요하다.
- queue head skip은 별도 ordering risk라 `03+02_queue_skip`로 분리했다.

### 범위 결정 근거

- OpenAI input estimation은 01 범위다. 이 plan은 typed `ContextClass`가 존재한다고 보고 admission에 사용한다.
- dev inventory/runtime option 실제 조정은 05 범위다.
- proto/status field 추가는 04 범위다. 이 plan의 snapshot 검증은 가능하면 service-local struct/state에 한정한다.

### 빌드 등급

- `local-G08`: config schema와 provider pool queue admission을 함께 바꾸는 넓은 변경이지만 deterministic Go tests와 config check로 검증 가능하다.

## 의존 관계 및 구현 순서

- `01_input_estimator` complete 필요: `agent-task/m-model-group-long-context-admission/01_input_estimator/complete.log`.
- predecessor가 없으면 구현하지 말고 active plan 상태를 유지한다.

## 구현 체크리스트

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

## [LONG_CAPACITY-1] Provider long capacity config와 validation

### 문제

- [packages/go/config/config.go](/config/workspace/iop/packages/go/config/config.go:420) 부근의 provider validation은 capacity/queue/priority 중심이며 `long_context_capacity`와 `total_context_tokens`가 없다.
- [configs/edge.yaml](/config/workspace/iop/configs/edge.yaml:120)의 model group은 `context_window_tokens`를 갖지만 provider별 budget 연결이 없다.

### 해결 방법

- `NodeProviderConf`에 `TotalContextTokens int`, `LongContextCapacity int`를 추가한다.
- provider field는 non-negative로 검증한다. `long_context_capacity > 0`이면 `total_context_tokens > 0`이어야 한다.
- model catalog provider mapping을 기준으로 `context_window_tokens > 0`인 model group에서 참조한 provider마다 `total_context_tokens >= context_window_tokens * long_context_capacity`를 검증한다.
- `configs/edge.yaml` 예시와 dev provider entry에 값을 추가한다. 실제 dev runtime option 조정은 05에서 완료한다.

예상 field:

```go
type NodeProviderConf struct {
    Capacity            int `mapstructure:"capacity" yaml:"capacity,omitempty"`
    TotalContextTokens  int `mapstructure:"total_context_tokens" yaml:"total_context_tokens,omitempty"`
    LongContextCapacity int `mapstructure:"long_context_capacity" yaml:"long_context_capacity,omitempty"`
}
```

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

- [ ] `packages/go/config/config.go`: schema, validation, provider lookup helper.
- [ ] `packages/go/config/config_test.go`: valid config, negative values, insufficient total context tests.
- [ ] `configs/edge.yaml`: config example/update.
- [ ] `apps/edge/internal/configrefresh/classify.go`: live-apply diff paths.
- [ ] `apps/edge/internal/configrefresh/classify_test.go`: field classification tests.

### 테스트 작성

- [ ] `TestLoadEdgeProviderLongContextCapacityValidation`.
- [ ] `TestLoadEdgeRejectsProviderLongContextBudgetBelowModelWindow`.
- [ ] configrefresh test for `nodes["..."].providers["..."].long_context_capacity` and `total_context_tokens`.

### 중간 검증

```bash
go test -count=1 ./packages/go/config ./apps/edge/internal/configrefresh
```

## [LONG_CAPACITY-2] Long slot reserve/release와 provider exclusion

### 문제

- [apps/edge/internal/service/run_dispatch.go](/config/workspace/iop/apps/edge/internal/service/run_dispatch.go:550)는 provider candidates를 일반 capacity/priority 기준으로 만든다.
- [apps/edge/internal/service/model_queue.go](/config/workspace/iop/apps/edge/internal/service/model_queue.go:300)는 queue group의 일반 `inflight`만 관리한다.
- SDD D06은 long slot full이 normal 후보 제외 조건이 아니라고 고정한다.

### 해결 방법

- `candidateNode`에 provider long capacity와 현재 long in-flight를 담는다.
- `SubmitRunRequest.ContextClass == "long"`일 때만 long slot gate를 적용한다.
- long request dispatch 성공 시 일반 `inflight`와 long `inflight`를 함께 증가시키고, terminal path에서 둘 다 감소시킨다.
- 모든 provider의 long slot이 full이면 long 요청은 queue에 남고 queue reason을 저장할 수 있는 내부 field를 둔다. queue reason의 노출은 04에서 다룬다.
- normal request candidate filtering은 long slot state를 보지 않는다.

예상 gate:

```go
if req.ContextClass == "long" && cand.longContextCapacity > 0 && cand.longInFlight >= cand.longContextCapacity {
    continue
}
```

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

- [ ] `apps/edge/internal/service/run_dispatch.go`: candidates에 long capacity/current state 전달.
- [ ] `apps/edge/internal/service/model_queue.go`: long reserve/release, queue reason storage, snapshot helper.
- [ ] `apps/edge/internal/service/model_queue_test.go`: reserve/release/exclusion/normal 유지 tests.
- [ ] `apps/edge/internal/service/service_internal_test.go`: refresh 후 long capacity candidate 반영 test.

### 테스트 작성

- [ ] long request dispatch increments normal `inflight` and `long_in_flight`.
- [ ] terminal completion/error/cancel decrements both.
- [ ] provider A long full이면 long request는 provider B 또는 queue로 가고, normal request는 provider A 일반 capacity가 남으면 선택 가능.

### 중간 검증

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

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `packages/go/config/config.go` | LONG_CAPACITY-1 |
| `packages/go/config/config_test.go` | LONG_CAPACITY-1 |
| `configs/edge.yaml` | LONG_CAPACITY-1 |
| `apps/edge/internal/configrefresh/classify.go` | LONG_CAPACITY-1 |
| `apps/edge/internal/configrefresh/classify_test.go` | LONG_CAPACITY-1 |
| `apps/edge/internal/service/run_dispatch.go` | LONG_CAPACITY-2 |
| `apps/edge/internal/service/model_queue.go` | LONG_CAPACITY-2 |
| `apps/edge/internal/service/model_queue_test.go` | LONG_CAPACITY-2 |
| `apps/edge/internal/service/service_internal_test.go` | LONG_CAPACITY-2 |

## 최종 검증

```bash
go test -count=1 ./packages/go/config ./apps/edge/internal/configrefresh ./apps/edge/internal/edgevalidate ./apps/edge/internal/service
go run ./apps/edge/cmd/edge config check --config configs/edge.yaml
```

기대 결과: 모든 명령 PASS, config check는 `OK configs/edge.yaml`.

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