<!-- task=m-provider-availability-capacity-queue-foundation/02+01_edge_snapshot plan=0 tag=API -->

# Edge Snapshot Plan - API

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

`CODE_REVIEW-cloud-G06.md`의 구현 에이전트 소유 섹션을 반드시 채운다. 구현 후 검증을 실행하고 실제 출력, 설계 결정, 계획 대비 변경 사항을 기록한 뒤 active 파일을 남긴 채 리뷰 준비를 보고한다. 사용자 결정, 사용자 소유 외부 환경, scope 충돌로 막히면 review stub의 `사용자 리뷰 요청` 섹션에 근거와 재개 조건을 적고 멈춘다. 직접 사용자에게 질문하거나 `USER_REVIEW.md`, `complete.log`, archive 로그를 만들지 않는다. 명령 재실행이나 산출물 수집으로 해소 가능한 증거 공백은 사용자 리뷰 요청이 아니다.

## 배경

Edge는 현재 node registry와 generic run capability만 볼 수 있고, provider별 status/capacity/in-flight/queued snapshot은 typed contract로 갖고 있지 않다. `01_probe_contract`가 provider status를 생산한 뒤, 이 작업은 Edge가 provider 구현 세부를 몰라도 조회할 수 있는 snapshot 계약을 연결한다. capacity queue가 아직 완성되지 않았으므로 이 plan은 `queued=0` 기준선을 명시하고 이후 queue 작업이 값을 교체할 수 있게 한다.

## 사용자 리뷰 요청 흐름

구현 중 차단은 `CODE_REVIEW-cloud-G06.md`의 `사용자 리뷰 요청` 섹션에 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식으로 기록한다. 구현 에이전트는 채팅으로 직접 선택지를 내거나 사용자 입력 도구를 호출하지 않는다. code-review가 요청 타당성을 검증하고 실제 `USER_REVIEW.md` 작성을 소유한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/provider-availability-capacity-queue-foundation.md`
- Task ids:
  - `edge-snapshot`: Edge가 provider별 구현 세부를 모르고 Node의 provider 상태, capacity, in-flight, queued 값을 조회하거나 이벤트로 받을 수 있는 계약을 정리한다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-roadmap/current.md`
- `agent-roadmap/phase/inference-provider-extension/PHASE.md`
- `agent-roadmap/phase/inference-provider-extension/milestones/provider-availability-capacity-queue-foundation.md`
- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/project/domain/node/rules.md`
- `agent-ops/rules/project/domain/edge/rules.md`
- `agent-ops/rules/project/domain/platform-common/rules.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/node-smoke.md`
- `agent-test/local/edge-smoke.md`
- `agent-test/local/platform-common-smoke.md`
- `apps/node/internal/runtime/types.go`
- `apps/node/internal/node/node.go`
- `apps/node/internal/node/run_manager.go`
- `apps/node/internal/node/node_test.go`
- `apps/edge/internal/service/node_command.go`
- `apps/edge/internal/service/status_provider.go`
- `apps/edge/internal/service/service_test.go`
- `apps/edge/internal/controlplane/connector.go`
- `apps/edge/internal/opsconsole/status.go`
- `proto/iop/runtime.proto`
- `proto/iop/control.proto`

### 테스트 환경 규칙

test_env는 `local`이다. `node-smoke`, `edge-smoke`, `platform-common-smoke`를 모두 적용한다. proto 변경 시 `make proto`가 필수이며, generated Go diff는 원본 proto에서 만들어야 한다. 기본 검증은 `go test ./apps/node/... ./apps/edge/... ./packages/go/... ./proto/gen/...`이고, Edge status/control-plane snapshot이 바뀌므로 관련 edge service/controlplane 테스트를 포함한다. 사용자 실행 파이프라인에 닿지만 이 plan은 query/status 계약 변경이므로 full-cycle 실제 구동은 구현 변경 규모에 따라 code-review에서 필수 여부를 다시 판단한다.

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

- `NodeCommandResponse`는 [proto/iop/runtime.proto](/config/workspace/iop/proto/iop/runtime.proto:103)의 free-form `result` map만 있고 typed provider snapshot payload가 없다.
- `EdgeNodeSnapshot`은 [proto/iop/control.proto](/config/workspace/iop/proto/iop/control.proto:77)에서 node/config만 담고 provider snapshot field가 없다.
- Edge service `NodeCommandView`는 [apps/edge/internal/service/node_command.go](/config/workspace/iop/apps/edge/internal/service/node_command.go:107)에서 result map만 보존한다.
- Control Plane status builder는 [apps/edge/internal/controlplane/connector.go](/config/workspace/iop/apps/edge/internal/controlplane/connector.go:367)에서 provider snapshot을 넣을 자리가 없다.

### 심볼 참조

none. 새 proto message/fields와 DTO field 추가가 중심이며 기존 symbol rename/remove는 없다.

### 분할 판단

split decision policy를 평가했다. shared task group은 `m-provider-availability-capacity-queue-foundation`이다. 이 subtask는 `02+01_edge_snapshot`이므로 predecessor index `01`이 필요하다. 현재 `agent-task/m-provider-availability-capacity-queue-foundation/01_probe_contract/complete.log`는 없어서 predecessor는 missing이다. 구현은 `01_probe_contract`가 PASS되어 `complete.log`가 생긴 뒤 시작해야 한다.

### 범위 결정 근거

이 subtask는 Edge가 조회할 수 있는 provider snapshot contract까지 다룬다. FIFO queue admission, `queue_timeout`, `max_queue` reject, request scheduling 정책은 capacity-queue Epic의 별도 task 범위다. queue가 없는 현재 기준선에서는 `queued=0`을 명시적으로 반환하고, queue task가 이 값을 실제 queue 길이로 교체하게 둔다.

### 빌드 등급

`cloud-G06`. proto schema, generated code, Node command, Edge service, Control Plane status가 함께 움직이는 cross-domain API 변경이다.

## 구현 체크리스트

- [ ] `01_probe_contract` predecessor의 `complete.log`가 있는지 확인하고 없으면 구현을 시작하지 않는다.
- [ ] provider snapshot typed proto를 추가하고 `make proto`로 Go generated files를 갱신한다.
- [ ] Node `CAPABILITIES` response가 status, capacity, in-flight, queued를 typed snapshot과 result map 양쪽에 일관되게 담도록 구현한다.
- [ ] Edge service DTO와 command path가 typed provider snapshot을 보존하도록 구현한다.
- [ ] Control Plane Edge status 또는 Edge-owned status surface가 provider snapshots를 노출하도록 구현한다.
- [ ] `go test ./apps/node/... ./apps/edge/... ./packages/go/... ./proto/gen/...`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

`02+01_edge_snapshot`은 directory name 기준으로 predecessor `01_probe_contract`에만 의존한다. 구현 전 `agent-task/m-provider-availability-capacity-queue-foundation/01_probe_contract/complete.log` 또는 matching archive `agent-task/archive/*/*/m-provider-availability-capacity-queue-foundation/01_probe_contract/complete.log`가 있어야 한다. 이 plan 작성 시점에는 missing이다.

### [API-1] Typed Provider Snapshot Proto

문제: [proto/iop/runtime.proto](/config/workspace/iop/proto/iop/runtime.proto:103)의 `NodeCommandResponse`는 map만 있고 provider snapshot의 `status`, `capacity`, `in_flight`, `queued`를 typed contract로 표현하지 못한다. [proto/iop/control.proto](/config/workspace/iop/proto/iop/control.proto:77)의 `EdgeNodeSnapshot`도 provider field가 없다.

해결 방법:

```proto
// before proto/iop/runtime.proto:103
message NodeCommandResponse {
  string request_id = 1;
  ...
  map<string, string> result = 8;
}
```

`runtime.proto`에 `ProviderSnapshot` message를 추가하고 `NodeCommandResponse`에 `repeated ProviderSnapshot provider_snapshots = 9;`를 추가한다. `control.proto`의 `EdgeNodeSnapshot`에는 `repeated ProviderSnapshot provider_snapshots = 6;`를 추가한다. `ProviderSnapshot.status`는 string으로 두되 값은 `unknown|available|unavailable`만 허용한다.

수정 파일 및 체크리스트:

- [ ] `proto/iop/runtime.proto`
- [ ] `proto/iop/control.proto`
- [ ] `proto/gen/iop/runtime.pb.go`는 `make proto`로만 갱신
- [ ] `proto/gen/iop/control.pb.go`는 `make proto`로만 갱신

테스트 작성: proto generated compile 검증으로 충분하다. 별도 proto unit test는 추가하지 않는다.

중간 검증:

```bash
make proto
```

### [API-2] Node Snapshot Population

문제: [apps/node/internal/node/node.go](/config/workspace/iop/apps/node/internal/node/node.go:317)는 string map만 채우며, [apps/node/internal/node/run_manager.go](/config/workspace/iop/apps/node/internal/node/run_manager.go:55)의 active count는 snapshot으로 노출되지 않는다.

해결 방법:

```go
// before apps/node/internal/node/node.go:317
result := map[string]string{
	"adapter":         caps.AdapterName,
	"instance_key":    caps.InstanceKey,
	"targets":         strings.Join(targets, ","),
	"max_concurrency": strconv.Itoa(caps.MaxConcurrency),
	"provider_status": string(runtime.NormalizeProviderStatus(caps.ProviderStatus)),
}
```

Node command response에 provider snapshot을 채운다. `capacity`는 `caps.MaxConcurrency`, `in_flight`는 해당 adapter permit의 `activeCount`, `queued`는 queue 미구현 기준선으로 `0`을 넣는다. result map에도 기존 console 호환을 위해 `capacity`, `in_flight`, `queued` keys를 추가한다.

수정 파일 및 체크리스트:

- [ ] `apps/node/internal/node/node.go`
- [ ] `apps/node/internal/node/run_manager.go` 필요 시 snapshot helper 추가
- [ ] `apps/node/internal/node/node_test.go`

테스트 작성: active run 중 `CAPABILITIES`를 호출해 `in_flight=1`, capacity 값, queued=0을 검증한다. status 값은 `01_probe_contract`의 probe 결과를 사용한다.

중간 검증:

```bash
go test ./apps/node/internal/node
```

### [API-3] Edge Service and Status Propagation

문제: [apps/edge/internal/service/node_command.go](/config/workspace/iop/apps/edge/internal/service/node_command.go:198)는 `resp.GetResult()`만 보존한다. [apps/edge/internal/service/status_provider.go](/config/workspace/iop/apps/edge/internal/service/status_provider.go:12)의 `NodeSnapshot`에도 provider snapshot field가 없다. [apps/edge/internal/controlplane/connector.go](/config/workspace/iop/apps/edge/internal/controlplane/connector.go:367)는 status response nodes에 provider data를 싣지 않는다.

해결 방법: `NodeCommandView`와 `NodeSnapshot`에 `ProviderSnapshots []*iop.ProviderSnapshot`를 추가한다. `sendNodeCommand`는 `resp.GetProviderSnapshots()`를 보존한다. Control Plane status path는 이미 Edge-owned snapshot DTO를 쓰므로 `EdgeNodeSnapshot.ProviderSnapshots`에 복사한다. live query가 필요한 곳은 `Service.Capabilities` 응답을 우선 사용하고, registry-only snapshot에는 nil을 허용한다.

수정 파일 및 체크리스트:

- [ ] `apps/edge/internal/service/node_command.go`
- [ ] `apps/edge/internal/service/status_provider.go`
- [ ] `apps/edge/internal/controlplane/connector.go`
- [ ] `apps/edge/internal/opsconsole/status.go`는 typed snapshot이 있으면 stable sorted render에 주요 keys가 보이도록 유지
- [ ] `apps/edge/internal/service/service_test.go`
- [ ] `apps/edge/internal/controlplane/connector_test.go`
- [ ] `apps/edge/internal/transport/server_test.go`

테스트 작성: service command test에서 provider snapshots가 preserved 되는지, controlplane status test에서 EdgeNodeSnapshot에 provider snapshots가 복사되는지 검증한다.

중간 검증:

```bash
go test ./apps/edge/internal/service ./apps/edge/internal/controlplane ./apps/edge/internal/transport
```

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `proto/iop/runtime.proto` | API-1 |
| `proto/iop/control.proto` | API-1 |
| `proto/gen/iop/runtime.pb.go` | API-1 |
| `proto/gen/iop/control.pb.go` | API-1 |
| `apps/node/internal/node/node.go` | API-2 |
| `apps/node/internal/node/run_manager.go` | API-2 |
| `apps/node/internal/node/node_test.go` | API-2 |
| `apps/edge/internal/service/node_command.go` | API-3 |
| `apps/edge/internal/service/status_provider.go` | API-3 |
| `apps/edge/internal/controlplane/connector.go` | API-3 |
| `apps/edge/internal/opsconsole/status.go` | API-3 |
| `apps/edge/internal/service/service_test.go` | API-3 |
| `apps/edge/internal/controlplane/connector_test.go` | API-3 |
| `apps/edge/internal/transport/server_test.go` | API-3 |

## 최종 검증

```bash
go test ./apps/node/... ./apps/edge/... ./packages/go/... ./proto/gen/...
```

기대 결과: 모든 listed package test가 통과한다. proto 변경 후 `make proto` 결과가 source proto와 일치해야 한다. Go test cache 출력은 허용하지 않는다. fresh 실행이 필요하면 구현 에이전트가 `go test -count=1 ...`로 대체하고 사유와 출력을 review stub에 기록한다.

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