# SDD: Node Resource Model Unification

## 위치

- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/node-resource-model-unification.md`
- Phase: `agent-roadmap/phase/operational-observability-provider-management/PHASE.md`

## 상태

[승인됨]

## SDD 잠금

- 상태: 해제
- 사용자 리뷰: 없음
- 잠금 항목:
  - 없음

## 문제 / 비목표

- 문제: 같은 Mac host에 Codex CLI와 MLX vLLM provider가 함께 있어도 현재 runtime과 dev profile은 이를 별도 Node처럼 취급할 수 있고, `runtime.concurrency`/global gate 의미가 Node를 shared capacity gate로 오해하게 만든다. 이 SDD는 Node를 Edge 연결 identity와 resource registry로 고정하고, provider/resource만 concurrency를 소유하도록 계약과 검증 기준을 고정한다.
- 비목표:
  - Node를 global scheduler, global resource gate, 또는 Control Plane canonical registry로 만들지 않는다.
  - provider/device/model qualification report나 lifecycle policy는 이번 Milestone에서 구현하지 않는다.
  - 새 public endpoint나 새 provider engine을 추가하지 않는다.
  - billing, org IAM, 장기 audit retention, 품질 평가 기반 routing은 다루지 않는다.

## Source of Truth

| 영역 | 기준 | 메모 |
|------|------|------|
| Roadmap | `agent-roadmap/phase/operational-observability-provider-management/milestones/node-resource-model-unification.md` | 목표, 기능 Task, 구현 잠금, 완료 판단 기준 |
| Contract | `agent-contract/inner/edge-node-runtime-wire.md`, `agent-contract/inner/edge-config-runtime-refresh.md` | Node runtime config, Edge-Node wire, config refresh 의미 기준 |
| Code | `proto/iop/runtime.proto`, `packages/go/config`, `apps/edge`, `apps/node`, `configs` | config validation, runtime admission, Edge dispatch/status 구현 기준 |
| External Provider | dev-runtime vLLM/MLX, Lemonade, vLLM provider endpoints | provider id, adapter instance, capacity smoke 기준 |
| User Decision | 없음 | 사용자가 Node/resource/provider 책임 경계와 provider-only concurrency 방향을 확정했다 |

## State Machine

| 상태 | 진입 조건 | 다음 상태 | 근거 |
|------|-----------|-----------|------|
| `node-configured` | `nodes[].id`와 adapter/resource catalog가 config에 존재한다 | `resource-catalog-validated` 또는 `config-error` | Edge config load/refresh |
| `resource-catalog-validated` | provider/resource가 같은 Node 안의 enabled adapter instance와 capacity/category를 참조한다 | `node-connected` | config validation result |
| `node-connected` | Node가 Edge에 연결되고 runtime config를 받는다 | `run-routed` 또는 `status-reported` | Edge-Node transport |
| `run-routed` | Edge가 CLI/direct request 또는 provider pool request를 받는다 | `provider-admitted` 또는 `direct-dispatched` | RunRequest routing fields |
| `provider-admitted` | request가 provider pool이고 provider capacity slot을 얻었다 | `provider-dispatched` 또는 `queued` | provider pool queue/admission |
| `direct-dispatched` | request가 CLI 또는 legacy direct route다 | `run-complete` 또는 `run-error` | Node adapter execution |
| `provider-dispatched` | Edge가 선택된 provider adapter target으로 dispatch했다 | `run-complete` 또는 `run-error` | provider adapter execution |
| `status-reported` | Edge가 Node/resource/provider snapshot을 집계한다 | 없음 | status provider snapshot |

## Interface Contract

- 계약 원문: `agent-contract/inner/edge-node-runtime-wire.md`, `agent-contract/inner/edge-config-runtime-refresh.md`
- 입력:
  - `nodes[].id`: Edge와 연결되는 Node identity다. 같은 host의 CLI와 provider resource가 이 id 아래에 함께 있을 수 있다.
  - `nodes[].alias`: 운영자가 인식하는 Node 별칭이다. routing source of truth는 id와 resource/provider id다.
  - `nodes[].adapters`: Node 안에서 실행 가능한 adapter instance 목록이다.
  - `nodes[].providers[]`: Node 아래 resource/provider catalog다. provider pool candidate와 status snapshot의 resource source로 사용한다.
  - `nodes[].providers[].adapter`: 같은 Node 안의 enabled adapter instance를 참조해야 한다.
  - `nodes[].providers[].capacity`: provider/resource admission capacity다. provider pool concurrency와 queue는 이 값을 기준으로 한다.
  - `NodeRuntimeConfig.concurrency` / `runtime.concurrency`: legacy/compat metadata다. Node-wide admission source로 사용하지 않는다.
  - `RunRequest.ProviderPool`: provider pool queue/admission을 사용할지 결정하는 flag다. `ModelGroupKey`만으로 queue에 진입하지 않는다.
- 출력:
  - provider/resource status snapshot: Node 아래 정의된 resource/provider catalog를 우선해 provider id, node id, model/capacity/in-flight/queued 상태를 표현한다.
  - direct execution result: CLI/direct route는 provider queue accounting을 증가시키지 않는다.
  - provider execution result: provider pool route는 provider capacity와 queue accounting을 반영한다.
- 금지:
  - Node id를 여러 resource의 global gate나 global concurrency bucket으로 쓰지 않는다.
  - CLI adapter를 IOP level concurrency로 제한하지 않는다.
  - `ModelGroupKey`가 있다는 이유만으로 CLI/direct route를 provider queue에 넣지 않는다.
  - `mac-mlx-vllm` provider를 dev-runtime에서 별도 IOP Node identity로 분리하지 않는다.
  - Control Plane을 Edge-local runtime registry의 canonical source로 만들지 않는다.

## Acceptance Scenarios

| ID | Milestone Task | Given | When | Then |
|----|----------------|-------|------|------|
| S01 | `contract-schema` | runtime contract와 config schema가 `NodeRuntimeConfig.concurrency`와 `nodes[].providers[]`를 포함한다 | 계약과 comments를 갱신한다 | node-wide concurrency가 legacy/compat metadata로 문서화되고 resource/provider capacity가 admission source로 문서화된다 |
| S02 | `contract-schema` | provider resource가 같은 Node의 disabled/missing adapter를 참조한다 | config validation을 실행한다 | validation error가 발생하고 enabled adapter reference만 통과한다 |
| S03 | `node-admission` | Node runtime concurrency가 1이고 서로 다른 CLI/provider resource가 있다 | 동시에 실행 요청을 보낸다 | Node global gate rejection 없이 adapter/resource capacity만 적용된다 |
| S04 | `node-admission` | CLI adapter capabilities를 조회한다 | status/capability snapshot을 만든다 | CLI `MaxConcurrency`는 unlimited 의미인 `0`으로 표현되고 provider adapters는 capacity gate를 유지한다 |
| S05 | `edge-routing-status` | CLI/direct request와 provider pool request가 모두 `ModelGroupKey`를 가진다 | Edge dispatch를 실행한다 | provider pool request만 queue를 사용하고 CLI/direct request는 direct dispatch된다 |
| S06 | `edge-routing-status` | Node가 resource catalog와 legacy adapter snapshot을 모두 가질 수 있다 | Edge status snapshot을 조회한다 | 정의된 resource/provider catalog가 우선되고 중복 adapter-level provider snapshot은 생성되지 않는다 |
| S07 | `dev-runtime-profile` | dev-runtime Mac host가 Codex CLI와 MLX vLLM provider를 실행한다 | clean deploy와 OpenAI-compatible capacity smoke를 실행한다 | connected Node는 `mac-codex-node`, `gx10-vllm-node`, `onexplayer-lemonade-node`이고 provider capacity total 10, queued >= 1, final in_flight/queued 0/0이 관측된다 |

## Evidence Map

| Scenario | Required Evidence | `agent-task` 연결 | 완료 Evidence 기대 |
|----------|-------------------|------------------|---------------------------|
| S01 | contract/proto/comment diff plus targeted tests | `agent-task/m-node-resource-model-unification/01_contract_schema` | `contract-schema` Roadmap Completion과 contract/config/proto verification output |
| S02 | config validation unit test | `agent-task/m-node-resource-model-unification/01_contract_schema` | `contract-schema` Roadmap Completion과 missing/disabled adapter validation output |
| S03 | Node concurrency regression test | `agent-task/m-node-resource-model-unification/02+01_node_admission` | `node-admission` Roadmap Completion과 global gate no-op evidence |
| S04 | CLI capability and provider capacity tests | `agent-task/m-node-resource-model-unification/02+01_node_admission` | `node-admission` Roadmap Completion과 CLI unlimited/provider gate output |
| S05 | Edge dispatch/provider queue tests | `agent-task/m-node-resource-model-unification/03+01_edge_dispatch_status` | `edge-routing-status` Roadmap Completion과 provider-only queue evidence |
| S06 | Edge status provider tests | `agent-task/m-node-resource-model-unification/03+01_edge_dispatch_status` | `edge-routing-status` Roadmap Completion과 resource catalog snapshot evidence |
| S07 | dev-runtime clean deploy and capacity smoke | `agent-task/m-node-resource-model-unification/04+01,02,03_dev_runtime_profile` | `dev-runtime-profile` Roadmap Completion과 `/v1/responses`, `/v1/chat/completions`, capacity accounting evidence |

## Cross-repo Dependencies

- 없음

## Drift Check

- [x] Milestone 기능 Task와 Acceptance Scenario가 일치한다.
- [x] Evidence Map이 code-review/complete.log에서 검증 가능하다.
- [x] agent-contract를 쓰는 경우 SDD에 계약 원문을 복제하지 않았다.
- [x] 사용자 리뷰가 필요한 항목은 `USER_REVIEW.md`에만 남겼다.

## 사용자 리뷰 이력

- 없음

## 작업 컨텍스트

- 표준선: Node는 Edge 연결 identity와 resource registry 책임을 가진다. CLI와 provider는 같은 Node 아래 resource로 공존할 수 있으며, concurrency는 provider/resource capacity가 소유한다.
- 표준선: provider pool queue는 `ProviderPool`이 true인 요청에서만 사용한다. CLI/direct route는 `ModelGroupKey`가 있어도 provider queue accounting을 사용하지 않는다.
- 후속 SDD: 요청 실행 로그와 Usage Ledger 기반이 provider/resource/node identity를 ledger schema로 확장할 때 이 SDD의 identity 기준을 참조한다.
