# Milestone: Provider Resource Admission Ownership 정합화

## 위치

- Roadmap: [ROADMAP.md](../../../ROADMAP.md)
- Phase: [PHASE.md](../PHASE.md)

## 목표

`nodes[].providers[]`가 선언한 실행 resource의 capacity와 long-context slot을 여러 `models[].id`가 같은 provider를 참조하더라도 하나의 provider 상태로 일관되게 적용하도록 admission 소유권을 바로잡는다.
Edge는 provider 전역 lease를 실행 한도의 authoritative source로 사용하고, Node는 normalized/tunnel 경로에 같은 adapter-local 안전장치를 적용해 model alias 추가나 우회 실행이 실제 backend 동시 실행 한도를 넘지 못하게 한다.
Node daemon이 최초 연결 실패와 장기 단절에도 원격 가용성만을 이유로 영구 종료되지 않게 하고, transport 연결 종료와 재수립을 같은 provider resource의 connectivity availability로 즉시 반영해 disconnect 시 새 admission 대상에서 제외하고 reconnect 시 후보와 대기 요청을 새 generation으로 복구하며, 운영 snapshot도 동일한 연결 상태로 수렴하도록 한다.

## 상태

[완료]

## 승격 조건

- 없음

## 구현 잠금

- 상태: 해제
- SDD: 필요
- SDD 문서: [SDD.md](../../../sdd/operational-observability-provider-management/provider-resource-admission-ownership-alignment/SDD.md)
- SDD 사유: provider 전역 admission 상태와 lease lifecycle, 여러 model queue의 공정한 재평가, queue config 소유권, Edge-Node 실행 경계와 live refresh 의미를 함께 변경한다.
- 잠금 해제 조건: 아래 체크리스트
  - [x] [에이전트 작업성 중심 저장소 구조 리팩터링](../../../archive/phase/automation-runtime-bridge/milestones/agent-readable-repository-refactor.md)이 완료되어 후속 구현 기준 파일 구조가 확정되어 있다.
  - [x] SDD 잠금이 해제되어 있다.
  - [x] SDD 사용자 리뷰가 없거나 승인/해결되었다.
  - [x] Acceptance Scenario가 Milestone 기능 Task와 연결되어 있다.
  - [x] Evidence Map이 완료 시 `Roadmap Completion`과 최종 검증 evidence로 검증 가능하게 연결되어 있다.
- 결정 필요: 없음

## 범위

- Edge admission의 provider resource state를 `node_id + provider_id` 기준 전역 상태로 두고 여러 model group이 capacity와 long-context 점유를 공유한다.
- provider lease 획득·handoff·반환을 원자적이고 idempotent하게 처리하고, send 실패, terminal event/frame, cancel, 연결 종료와 node disconnect가 같은 lease를 중복 반환하거나 누락하지 않게 한다.
- lease 반환과 disconnect fencing 같은 correctness 전이는 drop 가능한 관측 event fanout에 의존하지 않고 authoritative resource lifecycle에서 직접 처리한 뒤 event를 발행한다.
- lease 반환·provider 비활성화·node 연결 해제·capacity refresh 때 model group 경계를 넘어 가장 먼저 들어온 현재 dispatch 가능한 요청부터 재평가해 starvation과 head-of-line blocking을 함께 방지한다.
- provider별 실행 속성은 provider가 소유하고 여러 후보와 model group에 공통인 `max_queue`와 `queue_timeout_ms`는 Edge의 provider-pool admission policy가 소유하도록 정렬한다. provider 전역 capacity와 아직 provider가 정해지지 않은 Edge queue를 같은 상태로 혼동하지 않는다.
- `long_context_capacity`는 provider 전역 runtime slot으로 공유한다. `total_context_tokens`는 이번 범위에서 `context_window_tokens * long_context_capacity` 정적 설정 검증 기준으로 유지하며 요청별 KV token ledger로 확장하지 않는다.
- provider snapshot이 admission과 같은 resource state에서 `capacity`, `in_flight`, long-context 점유를 읽고, `queued`는 SDD에서 정한 queue owner와 candidate-pressure 의미로 일관되게 계산하도록 한다.
- Edge의 provider 전역 lease를 authoritative admission으로 두되, Node의 normalized `RunRequest`와 `ProviderTunnelRequest`에는 같은 adapter instance를 보호하는 local capacity gate를 별도로 둔다.
- config refresh 중 capacity·long-context capacity·priority·queue·enabled 변경이 기존 lease를 잃지 않고 이후 admission과 대기 후보 재평가에 일관되게 적용되도록 한다.
- Node daemon은 최초 dial/register 전부터 connectivity supervisor를 시작하고, retryable한 최초 연결 실패와 연결 수립 뒤 disconnect를 같은 reconnect policy로 처리한다. 명시적인 `reconnect.max_attempts=0`은 unlimited profile, 설정 생략은 기존 기본값 `10`, 양수는 유한 profile로 해석한다. unlimited profile에서는 양수 `interval_sec` cadence로 네트워크나 Edge가 오래 unavailable이어도 local shutdown까지 process를 유지하고, 유한 profile의 exhaustion과 non-retryable local config/credential 오류는 명시적인 non-zero terminal 결과로 구분한다. OS 시작 배치나 service는 Node process 실행만 담당하고 연결 복구 correctness를 대신 소유하지 않는다.
- Node 프로세스 또는 Windows의 정상 종료 절차가 transport close를 보낸 경우 heartbeat timeout을 기다리지 않고 해당 Node의 모든 provider resource를 admission 후보에서 제외한다. 전원 단절·네트워크 blackhole처럼 close를 받을 수 없는 경우에만 heartbeat timeout을 fallback으로 사용한다.
- Node disconnect로 대기 요청의 live candidate가 사라지면 남은 provider로 즉시 재평가하고, 후보가 하나도 없으면 원래 `queue_timeout_ms`까지 대기하지 않고 명시적인 unavailable 결과로 종료한다.
- 구성에는 남아 있지만 연결이 끊긴 Node/provider는 운영 snapshot에서 사라지게 하지 않고 offline/excluded 상태로 유지해 관측 상태와 admission eligibility가 같은 connectivity source of truth를 사용하도록 한다.
- accepted Node reconnect는 같은 resource identity의 새 generation을 admission 후보로 복구하고, 아직 대기 중인 관련 요청의 후보군을 live config/registry에서 다시 구성해 별도 외부 trigger 없이 전역 queue를 즉시 재평가한다.

## 기능

### Epic: [resource-admission] Provider 전역 Resource Lease

model group queue와 독립된 provider resource lease를 admission의 단일 source of truth로 만든다.

- [x] [provider-state] Edge가 `node_id + provider_id`별 capacity와 in-flight를 하나의 provider resource state로 관리하고, 서로 다른 두 `models[].id`가 같은 provider를 참조해도 합산 점유가 capacity를 넘지 않는다. 검증: capacity 1 provider를 공유하는 두 model group의 동시 admission에서 upstream dispatch가 한 건만 발생한다.
- [x] [lease-lifecycle] admission이 고유 provider lease를 만들고 dispatch 전 추적과 handoff 뒤 terminal lifecycle이 이를 정확히 한 번 반환한다. correctness 경로는 drop 가능한 event fanout과 분리한다. 검증: send 실패, complete/error/cancel, tunnel terminal/close와 node disconnect 경쟁 및 event subscriber 포화에서도 일반·long counter가 음수가 되거나 남지 않는다.
- [x] [long-state] long-context capacity와 점유가 provider 전역으로 합산되고 `total_context_tokens`는 정적 설정 검증값으로 유지된다. 검증: 서로 다른 model group의 long 요청이 같은 provider long slot을 동시에 초과하지 않고 잘못된 `context_window_tokens * long_context_capacity` 설정은 load/refresh에서 거부된다.
- [x] [cross-group-wakeup] provider lease 반환, node disconnect, provider disable 또는 capacity refresh 후 전역 enqueue 순서상 가장 이른 dispatch 가능한 요청부터 model group 경계를 넘어 재평가된다. 검증: 한 model group의 종료가 다른 model group의 대기 요청을 깨우고, 막힌 long 요청 뒤의 실행 가능한 normal 요청도 진행하며, starvation·중복 예약·slot 누수가 없다.
- [x] [tunnel-guard] Edge의 provider 전역 lease와 별개로 Node의 normalized 실행과 provider tunnel 실행이 같은 adapter-local capacity 안전장치를 사용한다. 검증: Edge admission을 우회한 동시 tunnel 요청에서도 두 번째 upstream provider 요청이 열리지 않고 명시적인 concurrency 결과가 반환된다.

### Epic: [provider-policy] Provider Policy 의미 정렬

provider config에 선언된 정책과 실제 Edge/Node 적용 위치의 불일치를 제거한다.

- [x] [queue-ownership] Edge의 `provider_pool.max_queue`와 `provider_pool.queue_timeout_ms`가 여러 model group에 공통인 provider-pool queue policy를 소유하고 schema, compatibility, admission과 refresh 경로에서 일관되게 적용된다. provider는 capacity, long-context capacity, priority처럼 provider별 실행 속성만 소유한다. 검증: 여러 provider가 있는 model group과 하나의 provider를 공유하는 여러 model group에서 Edge 전체 pending 상한과 공통 timeout이 적용되고 first-candidate 값이 전체 정책으로 암묵 승격되지 않는다.
- [x] [snapshot-source] provider snapshot의 일반·long `in_flight`가 provider lease state와 일치하고, `queued`/`long_queued`가 Edge queue에서 해당 provider를 후보로 포함하는 candidate pressure임을 명확히 한다. 검증: cross-model 실행·대기·반환 전 과정에서 lease count가 snapshot과 일치하고 multi-candidate queued 수가 실제 Edge queue 총합과 구분되는 문서화된 규칙대로 계산된다.
- [x] [refresh-consistency] capacity, long-context capacity, priority, queue policy와 enabled live refresh가 기존 lease를 보존하고 새 admission과 모든 관련 대기 요청에 즉시 반영된다. 검증: capacity를 in-flight 아래로 낮추면 기존 요청은 유지하되 새 값 아래로 회복할 때까지 추가 admission을 차단하고, capacity 증가·disable·재활성화 시 대기 후보가 고립되지 않는다.

### Epic: [connectivity-availability] Node 연결 기반 Provider Availability

Node transport lifecycle을 provider resource eligibility와 운영 관측의 같은 상태 전이로 연결한다.

- [x] [disconnect-exclusion] 등록된 client ownership/generation을 provider lease와 연결하고, 현재 owner의 정상 close 또는 disconnect만 해당 Node의 모든 provider resource를 즉시 offline/excluded로 fencing한다. stale callback이나 거절된 duplicate connection의 close는 live resource 상태와 disconnect event를 바꾸지 않는다. 정상적인 Node 프로세스 종료와 Windows 종료 절차는 heartbeat timeout을 기다리지 않으며, close를 받을 수 없는 전원·네트워크 장애만 설정된 heartbeat timeout 안에 제외한다. 검증: disconnect·reconnect·duplicate registration·admission 경쟁에서 offline generation의 새 lease/dispatch handoff가 없고 기존 lease는 정확히 한 번 반환된다.
- [x] [disconnect-queue-resolution] authoritative disconnect 전이가 모든 관련 model queue의 live candidate를 즉시 다시 해석해 남은 provider로 fallback하고, 후보가 없어진 요청은 `queue_timeout_ms`까지 남겨 두지 않고 명시적인 unavailable 결과로 종료한다. 이 correctness 경로는 관측 event delivery 성공 여부에 의존하지 않는다. 검증: capacity 1 provider에 실행·대기 요청이 있는 상태에서 연결을 닫거나 node event subscriber를 포화시켜도 대기 요청이 즉시 fallback 또는 terminal 상태가 되며 queue와 reservation이 남지 않는다.
- [x] [offline-snapshot] 구성에 존재하지만 연결이 끊긴 Node/provider를 운영 snapshot에 `connected=false`, `status=unavailable`, `health=offline`, effective capacity/counter 0으로 유지하고, reconnect 때 같은 resource identity의 새 generation으로 available 상태와 admission eligibility를 복구해 이전 generation의 orphan/excluded 상태가 새 generation 후보를 계속 막지 않게 한다. 검증: disconnect/reconnect 전후 status snapshot, admission candidate와 in-flight/long counter가 같은 connectivity generation에 수렴하며 model catalog entry 자체는 삭제되지 않는다.
- [x] [reconnect-candidate-recovery] accepted Node reconnect가 같은 resource identity의 새 generation을 live candidate로 복구하고, 아직 대기 중인 모든 관련 provider-pool 요청의 후보군을 live config/registry에서 다시 구성해 새 요청, config refresh 또는 다른 lease 반환을 기다리지 않고 전역 queue를 즉시 pump한다. 검증: 다른 live 후보가 full이라 대기를 유지한 요청이 별도 외부 trigger 없이 provider reconnect만으로 새 generation 후보를 얻어 즉시 dispatch되고, stale/rejected connection 전이는 후보 복구나 wake-up을 일으키지 않는다.
- [x] [node-connectivity-supervision] Node daemon이 최초 dial/register 전부터 connectivity supervisor를 실행해 retryable한 initial connect 실패와 연결 수립 뒤 disconnect를 같은 reconnect policy로 처리한다. 명시적인 `reconnect.max_attempts=0`은 unlimited, 설정 생략은 기존 기본값 `10`, 양수는 기존 호환 유한 limit, 음수는 config validation error로 정의한다. unlimited profile에는 생략 시 기본값 `10`이 적용되는 양수 `interval_sec`를 요구해 hot loop와 동시 dial을 막고, 네트워크나 Edge가 오래 unavailable이어도 local shutdown까지 한 번에 하나의 연결 시도만 bounded cadence로 재시도한다. 유한 retry exhaustion과 non-retryable local config/credential 오류는 명시적인 non-zero terminal 결과로 종료한다. OS 시작 배치나 service는 process 실행만 담당하며 연결 복구 correctness는 Node가 소유한다. 검증: 명시적 `max_attempts=0` Node를 네트워크/Edge보다 먼저 시작하고 기존 bounded retry 구간을 넘긴 뒤 Edge를 열어도 같은 process가 정확히 한 번 등록되고, `max_attempts` 생략은 10으로 유지되며, 양수 limit은 정확한 횟수 뒤 종료하고, 음수 max 또는 unlimited profile의 0 이하 interval은 거부되며, 정상 종료는 대기 중 retry를 즉시 중단한다.

### Epic: [ownership-verification] 계약과 회귀 검증

소유권 변경이 외부 model alias와 Edge-Node 실행 경로에서 재발하지 않도록 계약과 검증 근거를 남긴다.

- [x] [cross-model-tests] 같은 provider를 공유하는 `ornith:35b`와 `ornith-fast` 형태의 두 model key를 포함해 capacity, long-context, queue fairness, cancel, send 실패, delayed initial connect, long-outage reconnect, graceful disconnect, heartbeat-timeout fallback, duplicate/stale disconnect, event fanout 포화, reconnect된 provider의 기존 waiter 즉시 wake-up과 refresh 회귀·race 테스트가 추가되어 있다.
- [x] [contract-spec-sync] 구현 결과가 [Edge Config And Runtime Refresh Contract](../../../../agent-contract/inner/edge-config-runtime-refresh.md), [Edge-Node Runtime Wire Contract](../../../../agent-contract/inner/edge-node-runtime-wire.md), [Control Plane-Edge Wire Contract](../../../../agent-contract/inner/control-plane-edge-wire.md), [OpenAI-Compatible API Contract](../../../../agent-contract/outer/openai-compatible-api.md)과 관련 `agent-spec`에 동기화되어 있다.
- [x] [capacity-smoke] local 또는 dev-corp의 공유 provider capacity 1 smoke에서 두 model key 동시 호출 시 backend peak concurrency가 1이고, 대기 또는 다른 provider fallback 뒤 모든 provider 상태가 0으로 회복된다.

## 완료 리뷰

- 상태: 통과
- 요청일: 2026-07-22
- 완료 근거: SDD와 선행 리팩터링 gate가 충족됐고, 같은 task group의 archive `complete.log`·현재 코드/테스트·git 이력을 대조했다. 마지막 `capacity-smoke`는 [PASS complete.log](../../../../agent-task/archive/2026/07/m-provider-resource-admission-ownership-alignment/16_capacity_smoke/complete.log)의 local two-alias capacity-1 smoke에서 backend peak 1과 모든 provider counter 0 회복으로 확인됐다.
- 검토 항목: `make proto`, 관련 Go unit/race test, loopback capacity smoke, mock smoke와 직접 Edge-Node reconnect diagnostic이 통과했다. live provider preflight는 local endpoint 미가용으로 rc=3 차단 증거이며 S11 local smoke 완료를 대체하거나 무효화하지 않는다.
- 코드 감사: 통과. Required/Suggested 이슈 없음; shared provider lease, adapter-local tunnel guard, reconnect/offline snapshot과 Control Plane view 계약을 현재 코드·테스트로 재확인했다.
- Spec sync: Spec updated — [Edge-Node 실행 경로](../../../../agent-spec/runtime/edge-node-execution.md), [Provider Pool과 Config Refresh](../../../../agent-spec/runtime/provider-pool-config-refresh.md), [OpenAI-Compatible 입력 표면](../../../../agent-spec/input/openai-compatible-surface.md), [Control Plane 운영 기능](../../../../agent-spec/control/control-plane-operations.md).
- agent-ui 상태 반영: 해당 없음
- 리뷰 코멘트: 없음

## 범위 제외

- `ornith-fast` 모델 catalog entry 추가, RTX5090 모델 다운로드·기동 또는 운영 환경 배포
- Provider 부하 Prometheus metric과 Grafana dashboard 구현
- provider runtime launch profile, 모델 artifact 다운로드·cache·restart lifecycle 자동화
- Node 연결이 유지된 상태에서 Lemonade 등 provider endpoint를 능동 probe해 정적 config `health`를 대체하는 runtime health state machine
- provider `request_timeout_ms`와 surface `timeout_sec`의 deadline 결합·오류 분류 재설계
- 요청별 input/output KV 사용량을 예약·반환하는 동적 `total_context_tokens` ledger
- cross-Edge 또는 cloud provider를 포함한 전역 capacity 공유
- provider별 benchmark, 품질 평가와 qualification report

## 작업 컨텍스트

- 관련 경로: `apps/edge/internal/service`, `apps/edge/internal/transport`, `apps/edge/internal/controlplane`, `apps/edge/internal/openai`, `apps/node/internal/node`, `apps/node/internal/transport`, `packages/go/config`, `apps/edge/internal/configrefresh`, `proto/iop/runtime.proto`, `proto/iop/control.proto`, `agent-contract/inner`, `agent-contract/outer`, `agent-spec/runtime`
- 표준선(선택): provider/resource capacity, long-context slot, priority처럼 provider별로 달라지는 속성은 provider가 소유하고, provider-pool queue depth와 timeout처럼 공통 적용되는 정책은 Edge가 소유한다. model group은 외부 model key와 후보 집합을 소유한다.
- 큐 배치(선택): [에이전트 작업성 중심 저장소 구조 리팩터링](../../../archive/phase/automation-runtime-bridge/milestones/agent-readable-repository-refactor.md) 뒤
- 선행 작업: [에이전트 작업성 중심 저장소 구조 리팩터링](../../../archive/phase/automation-runtime-bridge/milestones/agent-readable-repository-refactor.md)
- 후속 작업: [Provider 부하 메트릭과 Live Queue Dashboard](provider-load-metrics-queue-dashboard.md), [Provider Runtime 설정과 모델 획득 오케스트레이션](provider-runtime-model-acquisition-orchestration.md)
- 후속 정합화 후보: Node 연결이 살아 있는 동안 provider endpoint/Lemonade 프로세스 장애를 탐지하는 runtime health probe source of truth와 provider/surface request timeout 결합 규칙은 별도 Milestone/SDD에서 다룬다.
- 확정 사항: D01은 Edge 공통 provider-pool queue policy 소유로 해결되었다. [사용자 리뷰 기록](../../../sdd/operational-observability-provider-management/provider-resource-admission-ownership-alignment/user_review_0.log)
