diff --git a/IOP_ROADMAP_STATUS.md b/IOP_ROADMAP_STATUS.md index f06f303..83958ea 100644 --- a/IOP_ROADMAP_STATUS.md +++ b/IOP_ROADMAP_STATUS.md @@ -1,6 +1,6 @@ # IOP 로드맵 현황 및 진행 계획 -작성 기준: 2026-06-18 +작성 기준: 2026-06-20 # 1. 월별 로드맵 @@ -21,11 +21,11 @@ | 구분 | 내용 | | --- | --- | -| 목표 | 같은 Edge 안에서 동일 `model` 요청을 model group queue로 묶고, 여러 Node 후보에 순차 dispatch하는 구조를 정리합니다. Lemonade 이후 vLLM과 SGLang provider 검증을 이어가며 1차 KPI 범위의 속도/디바이스 효율 축을 닫습니다. | +| 목표 | 같은 Edge 안에서 동일 `model` 요청을 model group queue로 묶고, 여러 Node 후보에 순차 dispatch하는 구조를 정리합니다. Lemonade 이후 vLLM과 SGLang provider 검증 결과를 반영해 1차 KPI 범위의 속도/디바이스 효율 축을 닫습니다. | | 예상 투입인원 | 1명 | -| 현재 구현 상태 | Node-local admission/capacity 기반 완료
Edge model group queue scheduling과 같은 Edge 내부의 다중 Node 후보 순차 dispatch 기반 완료
OpenAI-compatible `model` route queue 정책 정합화 완료
vLLM provider serving validation 완료: provider boundary, DGX Spark vLLM container, config contract, 모델 조회/chat, streaming, split-host field smoke 검증 PASS 근거와 코드 레벨 완료 리뷰까지 정리하고 archive함
SGLang provider serving validation을 다음 활성 마일스톤으로 지정함. 현재 계획/잠금 상태이며 SDD 승인과 provider 경계·실테스트 endpoint/model/auth/streaming 결정이 필요함 | -| 주요 작업 | 완료: OpenAI-compatible `model` 값을 queue group key로 사용하는 Edge-owned FIFO 정리
완료: Node 후보별 capacity, in-flight, queued snapshot을 기준으로 dispatch 흐름 정리
완료: Model route queue 정책 원천 정합화
완료: vLLM OpenAI-compatible provider의 config contract, 모델 조회, chat, streaming, split-host field smoke 검증
다음: SGLang provider SDD gate 해소와 최소 serving 경로 검증
계획: Provider별 adapter/config/target/model 매핑 기준을 vLLM/SGLang 검증 결과로 보강 | -| 완성 예정 기능 | vLLM provider 최소 serving 검증
SGLang provider 최소 serving 검증
Provider별 adapter/config/target/model 매핑 기준 보강 | +| 현재 구현 상태 | Node-local admission/capacity 기반 완료
Edge model group queue scheduling과 같은 Edge 내부의 다중 Node 후보 순차 dispatch 기반 완료
OpenAI-compatible `model` route queue 정책 정합화 완료
vLLM provider serving validation 완료: provider boundary, DGX Spark vLLM container, config contract, 모델 조회/chat, streaming, split-host field smoke 검증 PASS 근거와 코드 레벨 완료 리뷰까지 정리하고 archive함
SGLang provider serving validation은 GX10 + Qwen3.6 35B NVFP4 field validation 결과 NVIDIA checkpoint 직접 로딩 실패와 vLLM 대비 낮은 throughput/KV 여유가 확인되어 현 조건에서 provider 채택을 폐기함
다음 활성 작업은 운영 관측 Phase의 `Model Alias Provider Pool과 Provider Catalog` 계획 마일스톤으로 전환됨 | +| 주요 작업 | 완료: OpenAI-compatible `model` 값을 queue group key로 사용하는 Edge-owned FIFO 정리
완료: Node 후보별 capacity, in-flight, queued snapshot을 기준으로 dispatch 흐름 정리
완료: Model route Queue 정책 원천 정합화
완료: vLLM OpenAI-compatible provider의 config contract, 모델 조회, chat, streaming, split-host field smoke 검증
정리: SGLang provider는 현 field 조건에서 채택하지 않고 Provider Catalog/Qualification 후속 판단 후보로 분리
다음: `models[]` 기반 provider pool과 read-only Provider Catalog 계약 구현 계획 | +| 완성 예정 기능 | vLLM provider 최소 serving 검증 완료
SGLang provider field 판단 결과 정리
Provider별 adapter/config/target/model 매핑 기준을 Provider Catalog 계약으로 승계 | --- @@ -35,21 +35,21 @@ | --- | --- | | 목표 | IOP를 여러 Edge, Node, CLI Agent, local inference provider와 함께 운영하기 위해 필요한 사용자/토큰/사용량/로그/provider 상태 관찰 기준을 1차 feature MVP 단위로 정리합니다. | | 예상 투입인원 | 1명 | -| 현재 구현 상태 | Edge, Node, Control Plane, Client의 기본 상태 관찰 경로 구축
Provider availability와 capacity snapshot 기반 완료
사용자/토큰/사용량/로그 추적과 Provider Catalog는 스케치 상태이며, 사용자 검토 후 계획으로 승격 | -| 주요 작업 | 사용자 단위 API/CLI/local inference 사용량 집계 범위 정리
Token, request, execution, provider usage 로그의 최소 수집 항목 정의
Control Plane/Client 또는 CLI에서 확인할 운영 상태 항목 정리
API, CLI, local inference provider를 구분하는 Provider Catalog 기준 정리
vLLM, vLLM-MLX, Lemonade, SGLang 등 로컬 디바이스 provider 상태 표현 정리 | -| 완성 예정 기능 | 사용량, 토큰, 로그 운영 추적 MVP
Provider Catalog와 로컬 디바이스 상태 관리 MVP
운영자가 확인할 수 있는 provider/status/usage 최소 화면 또는 명령 표면 | +| 현재 구현 상태 | Edge, Node, Control Plane, Client의 기본 상태 관찰 경로 구축
Provider availability와 capacity snapshot 기반 완료
`Model Alias Provider Pool과 Provider Catalog`가 현재 활성 계획 마일스톤이며 SDD는 승인됨/잠금 해제 상태
사용자/토큰/사용량/로그 추적과 Provider-Device-Model Qualification 리포트는 후속 스케치 상태 | +| 주요 작업 | `models[]`를 OpenAI-compatible model key이자 provider pool canonical routing key로 정리
`nodes[].providers[]` 기반 provider 정의와 provider별 served model list 계약 정리
Edge-owned target rewrite와 `in_flight / capacity` load-ratio provider 선택 정책 구현 계획
API, CLI, local inference provider를 구분하는 read-only Provider Catalog 기준 정리
사용자 단위 API/CLI/local inference 사용량, token, request, execution 로그의 최소 수집 항목은 후속 MVP로 구체화 | +| 완성 예정 기능 | Model Alias Provider Pool과 Provider Catalog MVP
Provider Catalog와 로컬 디바이스 상태 관리 MVP
사용량, 토큰, 로그 운영 추적 MVP
운영자가 확인할 수 있는 provider/status/usage 최소 화면 또는 명령 표면 | --- -## 8월 ~ 9월: CLI Agent 알림, 원격 작업 환경, 단계 호출 검증 MVP +## 8월 ~ 9월: CLI Agent 알림, 원격 작업 환경, Update Plane, 단계 호출 검증 MVP | 구분 | 내용 | | --- | --- | -| 목표 | CLI Agent 사용량 limit 감지와 알림, 작업 자동 이어받기, workspace-bound execution 기반 원격 작업 환경을 feature MVP로 정리합니다. 동시에 planner/generator/verifier 형태의 단계 호출과 runtime schema 검증은 최소 실행 모드로 스케치합니다. | +| 목표 | CLI Agent 사용량 limit 감지와 알림, 작업 자동 이어받기, workspace-bound execution 기반 원격 작업 환경을 feature MVP로 정리합니다. 동시에 Edge/Node 자체 업데이트 기반과 planner/generator/verifier 형태의 단계 호출/runtime schema 검증은 최소 실행 모드로 스케치합니다. | | 예상 투입인원 | 1명 | -| 현재 구현 상태 | CLI runtime과 usage checker 계열 기반 마련
알림 자체는 `nexo`의 messaging/notification 기반을 활용할 수 있는 상태
socket/protocol 기반은 `proto-socket`의 안정화 결과를 활용할 수 있는 상태
CLI Agent 알림/자동 이어받기, 원격 작업 환경, 단계 호출 검증 MVP는 스케치 상태이며 사용자 검토 후 계획으로 승격 | -| 주요 작업 | CLI Agent limit 도달 이벤트와 사용자 알림 trigger 기준 정리
자동 이어받기 허용 조건, 중단 조건, 사용자 승인 경계 정리
workspace-bound execution을 이용한 원격 코딩/유지보수 작업 환경의 최소 운영 흐름 정리
요청 의도 분석, 실제 작업, 검증/schema 강제, 오류 시 retry/fallback의 최소 단계 실행 흐름 정리
Nexo 알림 기반과 IOP 실행 이벤트의 연동 경계 정리 | -| 완성 예정 기능 | CLI Agent 사용량 알림 MVP
CLI Agent 자동 이어받기 MVP
원격 코딩/유지보수 작업 환경 MVP
단계 호출과 runtime schema 검증 MVP | +| 현재 구현 상태 | CLI runtime과 usage checker 계열 기반 마련
OpenAI Workspace Agent Execution Contract 완료
알림 자체는 `nexo`의 messaging/notification 기반을 활용할 수 있는 상태
socket/protocol 기반은 `proto-socket`의 안정화 결과를 활용할 수 있는 상태
CLI Agent 알림/자동 이어받기, 원격 작업 환경, Update Plane, 단계 호출 검증 MVP는 스케치/계획 상태이며 사용자 검토 후 구체화 | +| 주요 작업 | CLI Agent limit 도달 이벤트와 사용자 알림 trigger 기준 정리
자동 이어받기 허용 조건, 중단 조건, 사용자 승인 경계 정리
workspace-bound execution을 이용한 원격 코딩/유지보수 작업 환경의 최소 운영 흐름 정리
Control Plane release view, Edge/Node update protocol, host-local manager, rollout/recovery 정책 스케치
요청 의도 분석, 실제 작업, 검증/schema 강제, 오류 시 retry/fallback의 최소 단계 실행 흐름 정리
Nexo 알림 기반과 IOP 실행 이벤트의 연동 경계 정리 | +| 완성 예정 기능 | CLI Agent 사용량 알림 MVP
CLI Agent 자동 이어받기 MVP
원격 코딩/유지보수 작업 환경 MVP
Update Plane 안정 프로토콜과 자체 업데이트 기반 스케치
단계 호출과 runtime schema 검증 MVP | --- @@ -80,9 +80,9 @@ | 구분      | 주요 산출물                                                                                                                           | | ----------------| -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 5월 ~ 6월 중순 | Edge-Node 실행 기반, Ollama serving 안정화, Control Plane/Client 운영 기반, CLI Automation Runtime, Domain Agent registry/bootstrap, OpenAI Responses input surface, Workspace Agent Execution Contract, Workspace port/env 표준화, Lemonade provider 검증 결과 | -| 6월 중순 ~ 7월 | Edge model group queue scheduling과 model route queue 정책 정합화 결과, vLLM/SGLang provider 검증 결과, provider별 adapter/config/target/model 매핑 기준                                                                    | -| 7월 ~ 8월   | 사용량/토큰/로그 추적 MVP 정의, Provider Catalog 초안, 로컬 디바이스 provider 상태 관리 기준, 운영 확인 표면 초안                                                                        | -| 8월 ~ 9월   | CLI Agent 사용량 알림/자동 이어받기 MVP, 원격 작업 환경 MVP, 단계 호출과 runtime schema 검증 MVP, Nexo 알림 연동 경계                                                                      | +| 6월 중순 ~ 7월 | Edge model group queue scheduling과 model route queue 정책 정합화 결과, vLLM provider 검증 결과, SGLang provider field 판단 결과, provider별 adapter/config/target/model 매핑 기준                                                                    | +| 7월 ~ 8월   | Model Alias Provider Pool과 Provider Catalog MVP, 사용량/토큰/로그 추적 MVP 정의, 로컬 디바이스 provider 상태 관리 기준, 운영 확인 표면 초안                                                                        | +| 8월 ~ 9월   | CLI Agent 사용량 알림/자동 이어받기 MVP, 원격 작업 환경 MVP, Update Plane 안정 프로토콜/자체 업데이트 기반, 단계 호출과 runtime schema 검증 MVP, Nexo 알림 연동 경계                                                                      | | 9월 ~ 10월   | 1차 KPI 범위 feature MVP 통합 검증 결과, E2E smoke 기준, 운영 전환 체크리스트, known limitation, 2차 범위 분리 결과                                                                       | | 10월 이후   | 장기 기억/RAG, advisor/context hook, 원격 터널링, oto scheduler/CI-CD, cloud fallback/품질 평가 고도화 검토 결과                                                                        | @@ -92,7 +92,7 @@ | 리스크 및 고려사항 | 내용 | | --- | --- | -| Provider 실장비 검증 | vLLM, SGLang, vLLM-MLX 등 local inference provider는 adapter 구현보다 실제 장비와 런타임별 동작 검증이 일정 변수입니다. 1차 범위에서는 provider별 전체 최적화보다 최소 serving 경로와 상태 표현을 우선합니다. | +| Provider 실장비 검증 | vLLM, SGLang, vLLM-MLX 등 local inference provider는 adapter 구현보다 실제 장비와 런타임별 동작 검증이 일정 변수입니다. SGLang은 현 field 조건에서 채택하지 않기로 정리했으며, 1차 범위에서는 provider별 전체 최적화보다 최소 serving 경로와 상태 표현을 우선합니다. | | 사용량/토큰/로그 정책 결정 | 사용자 단위 usage, token, log 추적은 구현보다 보존 범위, 노출 범위, 제한 정책 결정이 중요합니다. Billing, chargeback, 조직 IAM, 장기 retention은 1차 범위에서 완성하지 않고 후속 구체화로 둡니다. | | CLI Agent 자동 이어받기 안전 경계 | 자동 이어받기는 편의성이 크지만, 무제한 재시도나 의도하지 않은 실행 지속으로 이어지면 위험합니다. 1차에서는 limit 감지, 알림, 명시된 조건에서의 이어받기만 MVP로 다룹니다. | | Nexo와 Proto Socket 기반 활용 | 알림 기반은 `nexo`, socket/protocol 기반은 `proto-socket`의 거의 완료된 기반을 활용하는 전제로 봅니다. IOP에서는 새 인프라를 다시 만들기보다 실행 이벤트, 알림 trigger, protocol compatibility, 운영 표면 고정에 집중합니다. | @@ -104,10 +104,10 @@ IOP는 현재 Control Plane - Edge - Node 기반의 실행 오케스트레이션 구조와 OpenAI-compatible 실행 표면을 상당 부분 구축한 상태입니다. Edge-Node 실행 경로, Ollama serving, Control Plane/Client 운영 기반, CLI Automation Runtime, Domain Agent registry/bootstrap, OpenAI Responses input surface, workspace-bound agent execution contract, Workspace port/env 표준화, Node multi-target serving, provider availability/capacity queue, Lemonade provider 검증, Edge model group queue scheduling, model route queue 정책 정합화까지 완료되었습니다. -현재 vLLM provider serving validation은 완료 및 archive 처리되었습니다. Edge model group queue scheduling과 model route queue 정책 정합화는 완료된 기반으로 보고, 다음 활성 마일스톤은 SGLang provider serving validation입니다. SGLang은 현재 계획/잠금 상태이며, SDD 승인과 provider 경계·실테스트 endpoint/model/auth/streaming 결정이 다음 진입 조건입니다. +현재 vLLM provider serving validation은 완료 및 archive 처리되었습니다. SGLang provider serving validation은 field validation 결과를 반영해 현 조건에서는 provider 채택을 폐기했습니다. Edge model group queue scheduling과 model route queue 정책 정합화는 완료된 기반으로 보고, 다음 활성 마일스톤은 운영 관측 Phase의 `Model Alias Provider Pool과 Provider Catalog`입니다. -이후 7월부터 8월까지는 사용자/토큰/사용량/로그 추적과 Provider Catalog, 로컬 디바이스 provider 상태 관리를 정리합니다. 이 단계는 완성된 billing이나 enterprise IAM이 아니라, 운영자가 IOP의 사용량과 provider 상태를 확인할 수 있는 feature MVP를 목표로 합니다. +이후 7월부터 8월까지는 `models[]` 기반 provider pool, Edge-owned target rewrite, load-ratio provider 선택, read-only Provider Catalog, 로컬 디바이스 provider 상태 관리를 정리합니다. 이 단계는 완성된 billing이나 enterprise IAM이 아니라, 운영자가 IOP의 사용량과 provider 상태를 확인할 수 있는 feature MVP를 목표로 합니다. 사용자/토큰/사용량/로그 추적과 Provider-Device-Model Qualification 리포트는 후속 마일스톤으로 이어집니다. -8월부터 9월까지는 CLI Agent 사용량 limit 알림, 자동 이어받기, 원격 코딩/유지보수 작업 환경, 단계 호출과 runtime schema 검증 MVP를 구체화합니다. 알림 기반은 `nexo`, socket/protocol 기반은 `proto-socket`의 거의 완료된 기반을 활용하고, IOP는 실행 이벤트와 운영 정책, E2E 흐름 고정에 집중합니다. +8월부터 9월까지는 CLI Agent 사용량 limit 알림, 자동 이어받기, 원격 코딩/유지보수 작업 환경, Update Plane 자체 업데이트 기반, 단계 호출과 runtime schema 검증 MVP를 구체화합니다. 알림 기반은 `nexo`, socket/protocol 기반은 `proto-socket`의 거의 완료된 기반을 활용하고, IOP는 실행 이벤트와 운영 정책, 업데이트 수렴, E2E 흐름 고정에 집중합니다. 9월부터 10월까지는 1차 KPI 범위에 포함된 feature MVP를 통합 검증하고 안정화합니다. 10월 이후에는 장기 기억/RAG, advisor, context hook, 원격 터널링, oto scheduler/CI-CD, cross-Edge/cloud fallback 같은 2차 고도화 후보를 별도로 검토합니다. diff --git a/agent-roadmap/ROADMAP.md b/agent-roadmap/ROADMAP.md index a154dfd..d6c07c5 100644 --- a/agent-roadmap/ROADMAP.md +++ b/agent-roadmap/ROADMAP.md @@ -47,7 +47,7 @@ provider/device/model별 qualification report와 모델 lifecycle 관리는 prov - 경로: `agent-roadmap/phase/inference-provider-extension/PHASE.md` - 요약: 1차 MVP의 속도 향상 축으로, 같은 Edge 안의 model group queue와 여러 Node 후보 순차 dispatch를 기준으로 다중 디바이스 효율을 높이고 Lemonade/vLLM/SGLang 같은 provider 검증을 이어가는 단계다. -- [계획] 운영 관측과 Provider 관리 +- [진행중] 운영 관측과 Provider 관리 - 경로: `agent-roadmap/phase/operational-observability-provider-management/PHASE.md` - 요약: 사용자/토큰/사용량/로그 추적과 API/CLI/local inference provider catalog, 로컬 디바이스 provider 상태 관리, provider/device/model qualification report와 모델 lifecycle 관리 방향을 MVP 운영 축과 후속 심화 축으로 스케치한다. diff --git a/agent-roadmap/phase/operational-observability-provider-management/PHASE.md b/agent-roadmap/phase/operational-observability-provider-management/PHASE.md index 1e8c1e7..01483ee 100644 --- a/agent-roadmap/phase/operational-observability-provider-management/PHASE.md +++ b/agent-roadmap/phase/operational-observability-provider-management/PHASE.md @@ -2,7 +2,7 @@ ## 상태 -[계획] +[진행중] ## 목표 @@ -16,7 +16,7 @@ provider 확장 Phase에서 검증한 Ollama, vLLM, SGLang, Lemonade 같은 추 완료, 검토중, 진행중, 계획, 스케치 순서로 두어 아래로 갈수록 미래 작업에 가까워지게 정렬한다. 스케치 Milestone은 아직 구현 가능한 계획이 아니므로 사용자 검토와 구체화 후 `[계획]`으로 승격한다. -- [계획] Model Alias Provider Pool과 Provider Catalog +- [진행중] Model Alias Provider Pool과 Provider Catalog - 경로: `agent-roadmap/phase/operational-observability-provider-management/milestones/provider-catalog-device-status.md` - 요약: `models[]`를 외부 model key이자 운영 catalog model로 두고, Node 하위 provider와 served model list를 기준으로 Edge가 provider 선택, load-ratio routing, target rewrite, read-only catalog 상태를 소유하는 MVP 계약을 구현 계획 대상으로 정리한다. diff --git a/agent-roadmap/phase/operational-observability-provider-management/milestones/provider-catalog-device-status.md b/agent-roadmap/phase/operational-observability-provider-management/milestones/provider-catalog-device-status.md index 9490ddb..5c1e210 100644 --- a/agent-roadmap/phase/operational-observability-provider-management/milestones/provider-catalog-device-status.md +++ b/agent-roadmap/phase/operational-observability-provider-management/milestones/provider-catalog-device-status.md @@ -14,7 +14,7 @@ provider/device/model별 qualification report와 benchmark/품질 비교는 이 ## 상태 -[계획] +[진행중] ## 승격 조건 @@ -52,27 +52,27 @@ provider/device/model별 qualification report와 benchmark/품질 비교는 이 `models[]` catalog model을 중심으로 provider 후보를 묶고 Edge가 실행 후보를 고르는 최소 산출물을 구현한다. -- [ ] [alias-contract] `models[].id`가 OpenAI-compatible `model` key이자 provider pool의 canonical routing key가 되며, `models[].providers`가 provider id -> served model name map으로 검증된다. -- [ ] [candidate-schema] `nodes[].providers[]`가 provider id, type, category, served `models[]`, health, capacity, in-flight, queued, coarse lifecycle capability를 표현한다. -- [ ] [target-rewrite] Edge가 선택한 provider id의 `models[].providers[provider_id]` 값을 concrete target으로 rewrite해 Node에 전달하고, Node/provider adapter는 rewrite하지 않는다. -- [ ] [selection-policy] Edge가 available provider 중 `in_flight / capacity` load ratio가 가장 낮은 후보를 우선 선택하고, 동률은 deterministic tie-break로 처리하며, queue timeout을 적용한다. -- [ ] [pool-compat] 기존 `openai.model_routes`, `adapter + target`, Node provider instance 설정과 새 `models[]`/provider catalog 계약의 호환/마이그레이션 방향이 정리되어 있다. +- [x] [alias-contract] `models[].id`가 OpenAI-compatible `model` key이자 provider pool의 canonical routing key가 되며, `models[].providers`가 provider id -> served model name map으로 검증된다. +- [x] [candidate-schema] `nodes[].providers[]`가 provider id, type, category, served `models[]`, health, capacity, in-flight, queued, coarse lifecycle capability를 표현한다. +- [x] [target-rewrite] Edge가 선택한 provider id의 `models[].providers[provider_id]` 값을 concrete target으로 rewrite해 Node에 전달하고, Node/provider adapter는 rewrite하지 않는다. +- [x] [selection-policy] Edge가 available provider 중 `in_flight / capacity` load ratio가 가장 낮은 후보를 우선 선택하고, 동률은 deterministic tie-break로 처리하며, queue timeout을 적용한다. +- [x] [pool-compat] 기존 `openai.model_routes`, `adapter + target`, Node provider instance 설정과 새 `models[]`/provider catalog 계약의 호환/마이그레이션 방향이 정리되어 있다. ### Epic: [provider-catalog] Provider Operations Catalog 운영자가 model provider pool과 provider 상태를 같은 기준으로 비교하기 위한 read-only catalog 산출물을 묶는다. -- [ ] [category-map] MVP provider category가 `api`, `cli`, `local_inference`로 정리되고 read-only catalog 표시 필드에 반영된다. +- [x] [category-map] MVP provider category가 `api`, `cli`, `local_inference`로 정리되고 read-only catalog 표시 필드에 반영된다. - [ ] [device-status] provider health, capacity, in-flight, derived load ratio, served models, queued 상태가 catalog/status 표면에 정리된다. -- [ ] [lifecycle-cap] Ollama, Lemonade, vLLM, SGLang 등의 lifecycle 차이가 coarse capability flags로 표시되고 상세 qualification report는 후속 범위로 분리된다. -- [ ] [boundary] provider adapter 구현, Edge-owned provider pool routing, read-only operations catalog, 후속 runtime control의 책임 경계가 정리되어 있다. +- [x] [lifecycle-cap] Ollama, Lemonade, vLLM, SGLang 등의 lifecycle 차이가 coarse capability flags로 표시되고 상세 qualification report는 후속 범위로 분리된다. +- [x] [boundary] provider adapter 구현, Edge-owned provider pool routing, read-only operations catalog, 후속 runtime control의 책임 경계가 정리되어 있다. - [ ] [ops-review] Edge config와 Edge-owned read-only catalog/status를 기준으로 `iop-edge` CLI, Control Plane, Client가 어떤 정보를 소비하는지 확인할 수 있다. ## 완료 리뷰 - 상태: 없음 - 요청일: 없음 -- 완료 근거: 계획 Milestone이며 model provider pool과 catalog 기능 Task가 아직 충족되지 않았다. +- 완료 근거: 진행중 Milestone이며 `agent-task/archive/2026/06/m-provider-catalog-device-status/01_pool_schema/complete.log`와 `agent-task/archive/2026/06/m-provider-catalog-device-status/02+01_edge_dispatch/complete.log`의 Roadmap/Spec Completion 근거로 `alias-contract`, `candidate-schema`, `target-rewrite`, `selection-policy`를 완료 반영했다. `pool-compat`는 `01_pool_schema` 계획의 API-3 compatibility 산출물, `apps/edge/README.md`와 `configs/edge.yaml`의 migration/compat note, `TestLoadEdge_ProviderPoolLegacyCompatibility` 및 OpenAI provider-pool legacy fallback tests를 S05 보조 근거로 완료 반영했다. Provider Operations Catalog는 `packages/go/config`, `proto/iop/runtime.proto`, `apps/edge/internal/service/model_queue.go`, `apps/edge/README.md`, `configs/edge.yaml`, `apps/edge/internal/edgecmd/nodes.go` 근거로 `category-map`, `lifecycle-cap`, `boundary`를 완료 반영했으며, `device-status`와 `ops-review`는 Control Plane/Client 표면 계획이 남아 있다. - 리뷰 필요: - [ ] 사용자가 완료 결과를 확인했다 - [ ] archive 이동을 승인했다 @@ -97,4 +97,7 @@ provider/device/model별 qualification report와 benchmark/품질 비교는 이 - 선행 작업: Node provider 상태와 Capacity Queue 기반, Edge 모델 그룹 Queue 스케줄링 전환 - 후속 작업: provider pool scheduler 구현, Provider-Device-Model Qualification 리포트와 Lifecycle 관리, provider enable/disable, benchmark/품질 평가 - 작업 메모: [pool-compat] `apps/edge/README.md`와 `configs/edge.yaml`에 기존 `openai.model_routes`/`adapter + target`/Node provider instance 설정을 새 `models[]` provider pool의 1:1 migration 입력 및 fallback/compat 경로로 유지하는 방향을 정리했다. SDD 대상 Milestone의 공식 Task 체크는 `complete.log`의 Roadmap/Spec Completion 근거로 처리한다. +- 작업 메모: [sync] `01_pool_schema` 완료 로그로 `alias-contract`, `candidate-schema`를, `02+01_edge_dispatch` 완료 로그로 `target-rewrite`, `selection-policy`를 Roadmap/Spec Completion 기준 완료 처리했다. +- 작업 메모: [sync-correction] `pool-compat`는 `01_pool_schema`의 공식 Roadmap/Spec Targets에는 빠졌지만, SDD S05 Evidence Map이 요구한 migration/compat note와 기존 `model_routes` regression evidence가 `apps/edge/README.md`, `configs/edge.yaml`, `packages/go/config/config_test.go`, `apps/edge/internal/openai/server_test.go`에 남아 있어 완료로 보정했다. +- 작업 메모: [provider-catalog-small] `api`, `cli`, `local_inference` category는 `config.Category*`와 `ProviderSnapshot.category`에 반영되어 있고, coarse lifecycle capability는 `nodes[].providers[].lifecycle_capabilities`와 `ProviderSnapshot.lifecycle_capabilities` 및 `iop-edge nodes list` configured provider 요약으로 확인 가능하다. 상세 lifecycle/qualification과 runtime control은 SDD/범위 제외 기준에 따라 후속 Milestone으로 분리한다. - 확인 필요: 없음 diff --git a/agent-task/archive/2026/06/m-provider-catalog-device-status/03_catalog_status_http/code_review_local_G03_1.log b/agent-task/archive/2026/06/m-provider-catalog-device-status/03_catalog_status_http/code_review_local_G03_1.log new file mode 100644 index 0000000..7eaeb2d --- /dev/null +++ b/agent-task/archive/2026/06/m-provider-catalog-device-status/03_catalog_status_http/code_review_local_G03_1.log @@ -0,0 +1,198 @@ + + +# Code Review Reference - REVIEW_API + +> **[IMPLEMENTING AGENT - READ FIRST] Filling in this file is the mandatory final step of implementation.** +> The task is NOT complete until every implementation-owned section below is filled in. +> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving. +> Fill implementation-owned sections, then stop with active files in place and report ready for review. +> If implementation is blocked by a selected SDD decision or selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with linked evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Environment/secret/service blockers, generic scope changes, repeated failures, and evidence gaps that a follow-up agent can close are normal follow-up issues, not user-review blockers by themselves. +> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation; record only SDD/Milestone lock decisions in `사용자 리뷰 요청` and stop for code-review. +> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume. +> Follow the ownership table at the bottom of this file for which sections you own. + +## 개요 + +date=2026-06-20 +task=m-provider-catalog-device-status/03_catalog_status_http, plan=1, tag=REVIEW_API + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/provider-catalog-device-status.md` +- Task ids: + - `device-status`: provider health, capacity, in-flight, derived load ratio, served models, queued 상태가 catalog/status 표면에 정리된다. +- Completion mode: check-on-pass + +## Spec Targets + +- SDD: `agent-roadmap/sdd/operational-observability-provider-management/provider-catalog-device-status/SDD.md` +- Acceptance scenarios: + - `S07`: task=`device-status`; evidence=`Control Plane HTTP status JSON exposes provider health, capacity, in_flight, derived load_ratio, served_models, queued without runtime/resource telemetry.` +- Completion mode: spec-check-on-pass + +## Archive Evidence Snapshot + +- Archived plan: `agent-task/m-provider-catalog-device-status/03_catalog_status_http/plan_local_G04_0.log` +- Archived review: `agent-task/m-provider-catalog-device-status/03_catalog_status_http/code_review_local_G04_0.log` +- Verdict: FAIL +- Required summary: + - `apps/control-plane/cmd/control-plane/http_views.go:158` uses `json:"load_ratio,omitempty"`, so a valid idle provider with `load_ratio=0` omits the required status field from `/edges/{id}/status` JSON. + - `apps/control-plane/cmd/control-plane/main_test.go:638` already has a second provider fixture with implicit zero load ratio, but raw JSON assertions do not verify that `load_ratio` is present and equals `0`. +- Affected files: + - `apps/control-plane/cmd/control-plane/http_views.go` + - `apps/control-plane/cmd/control-plane/main_test.go` +- Prior verification evidence: + - Implementer recorded `go test ./apps/control-plane/cmd/control-plane -run TestEdgeRegistryHTTPHandlersGetLiveStatus -count=1`, `go test ./apps/control-plane/... -count=1`, and `git diff --check` as passing. + - Review rerun confirmed `go test ./apps/control-plane/cmd/control-plane -run TestEdgeRegistryHTTPHandlersGetLiveStatus -count=1`, `go test ./apps/control-plane/... -count=1`, and `git diff --check` pass before this follow-up. +- Carryover: + - Roadmap Targets and Spec Targets remain unchanged. + - This follow-up still works toward SDD `S07` and Milestone task `device-status`. +- Narrow archive reread allowed if needed: + - `agent-task/m-provider-catalog-device-status/03_catalog_status_http/plan_local_G04_0.log` + - `agent-task/m-provider-catalog-device-status/03_catalog_status_http/code_review_local_G04_0.log` + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. +리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다. + +1. 판정을 append한다. +2. `CODE_REVIEW-local-G03.md` -> `code_review_local_G03_1.log`, `PLAN-local-G03.md` -> `plan_local_G03_1.log`로 아카이브한다. +3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-provider-catalog-device-status/03_catalog_status_http/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. +4. PASS이고 task group이 `m-`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다. +5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [REVIEW_API-1] Zero Load Ratio JSON 보존 | [x] | + +## 구현 체크리스트 + +- [x] `apps/control-plane/cmd/control-plane/http_views.go`에서 `providerSnapshotView.LoadRatio` JSON tag를 `json:"load_ratio"`로 바꿔 zero value도 직렬화한다. +- [x] `apps/control-plane/cmd/control-plane/main_test.go`에서 second provider raw JSON assertion에 `load_ratio` key 존재 여부와 `float64(0)` 값을 추가한다. +- [x] `go test ./apps/control-plane/cmd/control-plane -run TestEdgeRegistryHTTPHandlersGetLiveStatus -count=1`를 실행한다. +- [x] `go test ./apps/control-plane/... -count=1`와 `git diff --check`를 실행한다. +- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. +> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다. + +- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다. +- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_local_G03_1.log`로 아카이브한다. +- [x] active `PLAN-*-G??.md`를 `plan_local_G03_1.log`로 아카이브한다. +- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다. +- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. +- [x] PASS이면 active task 디렉터리 `agent-task/m-provider-catalog-device-status/03_catalog_status_http/`를 `agent-task/archive/YYYY/MM/m-provider-catalog-device-status/03_catalog_status_http/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. +- [x] PASS이고 task group이 `m-`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다. +- [x] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-provider-catalog-device-status/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. +- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-*-G??.md`와 `CODE_REVIEW-*-G??.md`를 작성하고 `complete.log`를 작성하지 않는다. +- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다. +- [ ] USER_REVIEW가 연결된 SDD/Milestone 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다. + +## 계획 대비 변경 사항 + +계획에 따라 정확한 2개 파일만 수정했다. 추가 변경 사항 없음. + +1. `apps/control-plane/cmd/control-plane/http_views.go`: `LoadRatio` JSON tag에서 `omitempty` 제거 +2. `apps/control-plane/cmd/control-plane/main_test.go`: second provider raw JSON에 `load_ratio: 0` assertion 추가 + +## 주요 설계 결정 + +없음. 단일 JSON tag 조정만 수행. + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 SDD 결정 또는 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 연결 대상: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- `LoadRatio` JSON tag가 `omitempty` 없이 zero value를 직렬화하는지 확인한다. +- raw JSON assertion이 second provider의 `load_ratio: 0` 존재 여부를 검증하는지 확인한다. +- SDD `S07`의 status JSON evidence가 계속 충족되는지 확인한다. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +필수 규칙: +- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다. +- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다. +- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다. +- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다. +- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다. + +### REVIEW_API-1 중간 검증 + +```text +$ go test ./apps/control-plane/cmd/control-plane -run TestEdgeRegistryHTTPHandlersGetLiveStatus -count=1 +ok iop/apps/control-plane/cmd/control-plane 0.008s +``` + +### 최종 검증 + +```text +$ go test ./apps/control-plane/... -count=1 +ok iop/apps/control-plane/cmd/control-plane 0.148s + +ok iop/apps/control-plane/internal/wire 1.487s + +$ git diff --check +(no output - no whitespace errors) +``` + +--- + +> **[IMPLEMENTING AGENT - BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?** +> If anything is blank, go back and fill it in before saving this file. +> Leave review-agent-only sections unchanged. + +## 섹션 소유권 + +| Section | Owner | Note | +|---------|-------|------| +| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute these | +| Roadmap Targets | Fixed at stub creation from plan when present | Implementing agent must not modify | +| Spec Targets | Fixed at stub creation from plan when present | Implementing agent must not modify | +| Archive Evidence Snapshot | Fixed at stub creation from follow-up plan | Implementing agent may narrowly reread only the listed archive paths if needed | +| 구현 항목별 완료 여부 | Implementing agent | Check `[ ]` to `[x]` only | +| 구현 체크리스트 | Implementing agent | Check `[ ]` to `[x]` only | +| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify | +| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholder text with actual content | +| 사용자 리뷰 요청 | Implementing agent | Keep `상태: 없음` unless a selected SDD/Milestone lock decision blocks implementation | +| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Review focus | +| 검증 결과 | Implementing agent | Paste actual command output | + +## 코드리뷰 결과 + +- 종합 판정: PASS +- 차원별 평가: + - Correctness: Pass + - Completeness: Pass + - Test coverage: Pass + - API contract: Pass + - Code quality: Pass + - Plan deviation: Pass + - Verification trust: Pass + - Spec conformance: Pass +- 발견된 문제: + - 없음 +- 다음 단계: + - PASS complete.log 작성 후 task directory archive 이동. diff --git a/agent-task/archive/2026/06/m-provider-catalog-device-status/03_catalog_status_http/code_review_local_G04_0.log b/agent-task/archive/2026/06/m-provider-catalog-device-status/03_catalog_status_http/code_review_local_G04_0.log new file mode 100644 index 0000000..eb18519 --- /dev/null +++ b/agent-task/archive/2026/06/m-provider-catalog-device-status/03_catalog_status_http/code_review_local_G04_0.log @@ -0,0 +1,206 @@ + + +# Code Review Reference - API + +> **[IMPLEMENTING AGENT - READ FIRST] Filling in this file is the mandatory final step of implementation.** +> The task is NOT complete until every implementation-owned section below is filled in. +> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving. +> Fill implementation-owned sections, then stop with active files in place and report ready for review. +> If implementation is blocked by a selected SDD decision or selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with linked evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Environment/secret/service blockers, generic scope changes, repeated failures, and evidence gaps that a follow-up agent can close are normal follow-up issues, not user-review blockers by themselves. +> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation; record only SDD/Milestone lock decisions in `사용자 리뷰 요청` and stop for code-review. +> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume. +> Follow the ownership table at the bottom of this file for which sections you own. + +## 개요 + +date=2026-06-20 +task=m-provider-catalog-device-status/03_catalog_status_http, plan=0, tag=API + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/provider-catalog-device-status.md` +- Task ids: + - `device-status`: provider health, capacity, in-flight, derived load ratio, served models, queued 상태가 catalog/status 표면에 정리된다. +- Completion mode: check-on-pass + +## Spec Targets + +- SDD: `agent-roadmap/sdd/operational-observability-provider-management/provider-catalog-device-status/SDD.md` +- Acceptance scenarios: + - `S07`: task=`device-status`; evidence=`Control Plane HTTP status JSON exposes provider health, capacity, in_flight, derived load_ratio, served_models, queued without runtime/resource telemetry.` +- Completion mode: spec-check-on-pass + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. +리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다. + +1. 판정을 append한다. +2. `CODE_REVIEW-local-G04.md` -> `code_review_local_G04_0.log`, `PLAN-local-G04.md` -> `plan_local_G04_0.log`로 아카이브한다. +3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-provider-catalog-device-status/03_catalog_status_http/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. `USER_REVIEW.md`가 연결된 SDD/Milestone 결정으로 완료/PASS 해소되면 code-review가 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log` 작성 후 archive 이동한다. +4. PASS이고 task group이 `m-`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다. +5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [API-1] Provider Snapshot JSON DTO 추가 | [x] | +| [API-2] Status JSON 회귀 테스트 보강 | [x] | + +## 구현 체크리스트 + +- [x] Control Plane HTTP status DTO에 provider snapshot read-only fields를 추가하고 `EdgeNodeSnapshot.ProviderSnapshots`를 JSON으로 전달한다. +- [x] `/edges/{id}/status` 테스트에 provider snapshot fixture와 JSON assertion을 추가해 `health`, `capacity`, `in_flight`, `load_ratio`, `served_models`, `queued`, `category`, `lifecycle_capabilities`가 보존되는지 확인한다. +- [x] raw config secret non-leak 검증이 provider snapshot 추가 후에도 유지되는지 확인한다. +- [x] `go test ./apps/control-plane/...`를 실행한다. +- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. +> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다. + +- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다. +- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_local_G04_0.log`로 아카이브한다. +- [x] active `PLAN-*-G??.md`를 `plan_local_G04_0.log`로 아카이브한다. +- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다. +- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. +- [ ] PASS이면 active task 디렉터리 `agent-task/m-provider-catalog-device-status/03_catalog_status_http/`를 `agent-task/archive/YYYY/MM/m-provider-catalog-device-status/03_catalog_status_http/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. +- [ ] PASS이고 task group이 `m-`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다. +- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-provider-catalog-device-status/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. +- [x] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-local-G03.md`와 `CODE_REVIEW-local-G03.md`를 작성하고 `complete.log`를 작성하지 않는다. +- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다. +- [ ] USER_REVIEW가 연결된 SDD/Milestone 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다. + +## 계획 대비 변경 사항 + +계획에 명시된 구현을 그대로 따랐습니다. 추가 변경 사항은 없습니다. + +- `providerSnapshotView` struct는 plan의 목표 코드와 동일하게 12개 필드로 추가 +- `providerSnapshotViews` helper는 proto getter만 사용하며 nil/empty slice는 nil 반환 +- `edgeStatusResponseViewFromProto`는 `n.GetProviderSnapshots()`를 `providerSnapshotViews()`로 변환하여 매핑 +- 테스트 fixture는 plan에 명시된 2개 provider snapshot (vllm-gpu, ollama-cpu) 추가 +- typed DTO assertion과 raw JSON assertion 모두 plan에 명시된 필드 검증 포함 + +## 주요 설계 결정 + +- `providerSnapshotView`는 pointer가 아닌 value slice (`[]providerSnapshotView`)로 정의하여, 빈 경우 `null`이 아닌 `nil` JSON으로 직렬화되도록 함 (omitempty 태그와 함께) +- `ServedModels`와 `LifecycleCapabilities`는 `append([]string(nil), ...)`로 복사하여 원본 슬라이스 수정 방지 +- `providerSnapshotViews` helper는 nil 입력 시 nil 반환하여 `json:"provider_snapshots,omitempty"`이 작동하도록 함 + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 SDD 결정 또는 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 연결 대상: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- `edgeStatusResponseViewFromProto`가 `n.GetProviderSnapshots()`를 누락하지 않는지 확인한다. +- `provider_snapshots` JSON은 read-only status/catalog 필드만 포함하고 Node token/address/raw adapter settings를 노출하지 않는지 확인한다. +- test fixture가 `load_ratio`, `served_models`, `lifecycle_capabilities`까지 검증하는지 확인한다. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +필수 규칙: +- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다. +- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다. +- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다. +- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다. +- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다. + +### API-1 중간 검증 + +```text +$ go test ./apps/control-plane/cmd/control-plane -run TestEdgeRegistryHTTPHandlersGetLiveStatus -count=1 +=== RUN TestEdgeRegistryHTTPHandlersGetLiveStatus +=== RUN TestEdgeRegistryHTTPHandlersGetLiveStatus/Success +=== RUN TestEdgeRegistryHTTPHandlersGetLiveStatus/MissingEdge +=== RUN TestEdgeRegistryHTTPHandlersGetLiveStatus/UpstreamErrorOrTimeout +--- PASS: TestEdgeRegistryHTTPHandlersGetLiveStatus (0.00s) + --- PASS: TestEdgeRegistryHTTPHandlersGetLiveStatus/Success (0.00s) + --- PASS: TestEdgeRegistryHTTPHandlersGetLiveStatus/MissingEdge (0.00s) + --- PASS: TestEdgeRegistryHTTPHandlersGetLiveStatus/UpstreamErrorOrTimeout (0.00s) +PASS +ok iop/apps/control-plane/cmd/control-plane 0.008s +``` + +### API-2 중간 검증 + +```text +$ go test ./apps/control-plane/cmd/control-plane -run TestEdgeRegistryHTTPHandlersGetLiveStatus -count=1 +=== RUN TestEdgeRegistryHTTPHandlersGetLiveStatus +=== RUN TestEdgeRegistryHTTPHandlersGetLiveStatus/Success +=== RUN TestEdgeRegistryHTTPHandlersGetLiveStatus/MissingEdge +=== RUN TestEdgeRegistryHTTPHandlersGetLiveStatus/UpstreamErrorOrTimeout +--- PASS: TestEdgeRegistryHTTPHandlersGetLiveStatus (0.00s) + --- PASS: TestEdgeRegistryHTTPHandlersGetLiveStatus/Success (0.00s) + --- PASS: TestEdgeRegistryHTTPHandlersGetLiveStatus/MissingEdge (0.00s) + --- PASS: TestEdgeRegistryHTTPHandlersGetLiveStatus/UpstreamErrorOrTimeout (0.00s) +PASS +ok iop/apps/control-plane/cmd/control-plane 0.008s +``` + +### 최종 검증 + +```text +$ go test ./apps/control-plane/... -count=1 +ok iop/apps/control-plane/cmd/control-plane 0.143s +ok iop/apps/control-plane/internal/wire 1.478s + +$ git diff --check +(no output - no whitespace errors) +``` + +--- + +> **[IMPLEMENTING AGENT - BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?** +> If anything is blank, go back and fill it in before saving this file. +> Leave review-agent-only sections unchanged. + +## 섹션 소유권 + +| Section | Owner | Note | +|---------|-------|------| +| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute these | +| Roadmap Targets | Fixed at stub creation from plan when present | Implementing agent must not modify | +| Spec Targets | Fixed at stub creation from plan when present | Implementing agent must not modify | +| 구현 항목별 완료 여부 | Implementing agent | Check `[ ]` to `[x]` only | +| 구현 체크리스트 | Implementing agent | Check `[ ]` to `[x]` only | +| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify | +| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholder text with actual content | +| 사용자 리뷰 요청 | Implementing agent | Keep `상태: 없음` unless a selected SDD/Milestone lock decision blocks implementation | +| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Review focus | +| 검증 결과 | Implementing agent | Paste actual command output | + +## 코드리뷰 결과 + +- 종합 판정: FAIL +- 차원별 평가: + - Correctness: Fail + - Completeness: Fail + - Test coverage: Fail + - API contract: Fail + - Code quality: Pass + - Plan deviation: Pass + - Verification trust: Pass + - Spec conformance: Fail +- 발견된 문제: + - Required: [apps/control-plane/cmd/control-plane/http_views.go](/config/workspace/iop/apps/control-plane/cmd/control-plane/http_views.go:158)의 `load_ratio,omitempty` 때문에 유효한 idle 상태값인 `load_ratio=0`이 `/edges/{id}/status` JSON에서 누락됩니다. SDD `S07`과 이 plan의 `Spec Targets`는 derived `load_ratio`가 status JSON에 노출되는 것을 요구하므로, `LoadRatio` JSON tag에서 `omitempty`를 제거하고 zero value가 직렬화되는지 보장해야 합니다. [apps/control-plane/cmd/control-plane/main_test.go](/config/workspace/iop/apps/control-plane/cmd/control-plane/main_test.go:638)의 두 번째 provider fixture가 이미 zero-load 케이스지만 raw JSON에서 `load_ratio` 존재 여부를 검증하지 않아 회귀가 잡히지 않습니다. `rawP1["load_ratio"]`가 존재하고 `float64(0)`인지 확인하는 assertion을 추가하세요. +- 다음 단계: + - FAIL 후속 plan/review를 작성한다. diff --git a/agent-task/archive/2026/06/m-provider-catalog-device-status/03_catalog_status_http/complete.log b/agent-task/archive/2026/06/m-provider-catalog-device-status/03_catalog_status_http/complete.log new file mode 100644 index 0000000..ea687a4 --- /dev/null +++ b/agent-task/archive/2026/06/m-provider-catalog-device-status/03_catalog_status_http/complete.log @@ -0,0 +1,51 @@ +# Complete - m-provider-catalog-device-status/03_catalog_status_http + +## 완료 일시 + +2026-06-20 + +## 요약 + +Control Plane provider status JSON 노출 작업을 2개 루프로 완료했다. 최종 판정: PASS. + +## 루프 이력 + +| Plan | Review | Verdict | 메모 | +|------|--------|---------|------| +| `plan_local_G04_0.log` | `code_review_local_G04_0.log` | FAIL | provider snapshot JSON 추가와 테스트 보강은 완료됐으나, `load_ratio=0`이 `omitempty`로 누락되는 API 계약 문제가 발견됐다. | +| `plan_local_G03_1.log` | `code_review_local_G03_1.log` | PASS | `load_ratio` zero value를 JSON에 보존하고 raw JSON regression assertion을 추가했다. | + +## 구현/정리 내용 + +- Control Plane `/edges/{id}/status` node JSON에 `provider_snapshots`를 추가해 provider health, capacity, in-flight, queued, served models, category, lifecycle capabilities, load ratio를 전달한다. +- provider snapshot 변환은 proto getter만 사용하고 string slice는 복사해 HTTP DTO로 격리했다. +- status handler test에 provider snapshot fixture, typed DTO assertion, raw JSON assertion, config/provider snapshot secret non-leak assertion을 추가했다. +- `load_ratio` JSON tag에서 `omitempty`를 제거해 idle provider의 `load_ratio=0`도 명시적으로 노출되도록 했다. + +## 최종 검증 + +- `go test ./apps/control-plane/cmd/control-plane -run TestEdgeRegistryHTTPHandlersGetLiveStatus -count=1` - PASS; `ok iop/apps/control-plane/cmd/control-plane 0.005s` +- `go test ./apps/control-plane/... -count=1` - PASS; `ok iop/apps/control-plane/cmd/control-plane 0.141s`, `ok iop/apps/control-plane/internal/wire 1.487s` +- `git diff --check` - PASS; no output. + +## Roadmap Completion + +- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/provider-catalog-device-status.md` +- Completed task ids: + - `device-status`: PASS; evidence=`agent-task/archive/2026/06/m-provider-catalog-device-status/03_catalog_status_http/plan_local_G03_1.log`, `agent-task/archive/2026/06/m-provider-catalog-device-status/03_catalog_status_http/code_review_local_G03_1.log`; verification=`go test ./apps/control-plane/cmd/control-plane -run TestEdgeRegistryHTTPHandlersGetLiveStatus -count=1`, `go test ./apps/control-plane/... -count=1`, `git diff --check` +- Not completed task ids: 없음 + +## Spec Completion + +- SDD: `agent-roadmap/sdd/operational-observability-provider-management/provider-catalog-device-status/SDD.md` +- Completed scenario ids: + - `S07`: PASS; task=`device-status`; evidence=`agent-task/archive/2026/06/m-provider-catalog-device-status/03_catalog_status_http/plan_local_G03_1.log`, `agent-task/archive/2026/06/m-provider-catalog-device-status/03_catalog_status_http/code_review_local_G03_1.log`; verification=`go test ./apps/control-plane/cmd/control-plane -run TestEdgeRegistryHTTPHandlersGetLiveStatus -count=1`, `go test ./apps/control-plane/... -count=1`, `git diff --check` +- Not completed scenario ids: 없음 + +## 잔여 Nit + +- 없음 + +## 후속 작업 + +- 없음 diff --git a/agent-task/archive/2026/06/m-provider-catalog-device-status/03_catalog_status_http/plan_local_G03_1.log b/agent-task/archive/2026/06/m-provider-catalog-device-status/03_catalog_status_http/plan_local_G03_1.log new file mode 100644 index 0000000..f41687b --- /dev/null +++ b/agent-task/archive/2026/06/m-provider-catalog-device-status/03_catalog_status_http/plan_local_G03_1.log @@ -0,0 +1,97 @@ + + +# PLAN REVIEW_API - Preserve Zero Load Ratio In Status JSON + +## 이 파일을 읽는 구현 에이전트에게 + +이 follow-up plan은 직전 코드리뷰의 Required 1건만 해결한다. 구현은 active `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션 작성까지 끝나야 완료다. 코드 변경 후 검증 명령을 실행하고 실제 stdout/stderr를 review stub에 붙인 뒤 active 파일을 그대로 두고 리뷰 준비를 보고한다. + +구현 중 선택된 SDD 결정 또는 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 막으면 review stub의 `사용자 리뷰 요청` 섹션에 연결 대상, 차단 근거, 실행한 명령, 재개 조건을 기록하고 멈춘다. 직접 사용자에게 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 환경, secret, service 준비, 일반 범위 변경, 증거 공백은 사용자 리뷰 요청이 아니라 검증 결과 또는 후속 plan 대상으로 기록한다. + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/provider-catalog-device-status.md` +- Task ids: + - `device-status`: provider health, capacity, in-flight, derived load ratio, served models, queued 상태가 catalog/status 표면에 정리된다. +- Completion mode: check-on-pass + +## Spec Targets + +- SDD: `agent-roadmap/sdd/operational-observability-provider-management/provider-catalog-device-status/SDD.md` +- Acceptance scenarios: + - `S07`: task=`device-status`; evidence=`Control Plane HTTP status JSON exposes provider health, capacity, in_flight, derived load_ratio, served_models, queued without runtime/resource telemetry.` +- Completion mode: spec-check-on-pass + +## Archive Evidence Snapshot + +- Archived plan: `agent-task/m-provider-catalog-device-status/03_catalog_status_http/plan_local_G04_0.log` +- Archived review: `agent-task/m-provider-catalog-device-status/03_catalog_status_http/code_review_local_G04_0.log` +- Verdict: FAIL +- Required summary: + - `apps/control-plane/cmd/control-plane/http_views.go:158` uses `json:"load_ratio,omitempty"`, so a valid idle provider with `load_ratio=0` omits the required status field from `/edges/{id}/status` JSON. + - `apps/control-plane/cmd/control-plane/main_test.go:638` already has a second provider fixture with implicit zero load ratio, but raw JSON assertions do not verify that `load_ratio` is present and equals `0`. +- Affected files: + - `apps/control-plane/cmd/control-plane/http_views.go` + - `apps/control-plane/cmd/control-plane/main_test.go` +- Prior verification evidence: + - Implementer recorded `go test ./apps/control-plane/cmd/control-plane -run TestEdgeRegistryHTTPHandlersGetLiveStatus -count=1`, `go test ./apps/control-plane/... -count=1`, and `git diff --check` as passing. + - Review rerun confirmed `go test ./apps/control-plane/cmd/control-plane -run TestEdgeRegistryHTTPHandlersGetLiveStatus -count=1`, `go test ./apps/control-plane/... -count=1`, and `git diff --check` pass before this follow-up. +- Carryover: + - Roadmap Targets and Spec Targets remain unchanged. + - This follow-up still works toward SDD `S07` and Milestone task `device-status`. +- Narrow archive reread allowed if needed: + - `agent-task/m-provider-catalog-device-status/03_catalog_status_http/plan_local_G04_0.log` + - `agent-task/m-provider-catalog-device-status/03_catalog_status_http/code_review_local_G04_0.log` + +## 분석 결과 + +### 빌드 등급 + +- lane/grade: `local-G03`. +- 근거: 단일 JSON tag 조정과 기존 HTTP handler test assertion 보강이다. 로컬 Go test와 `git diff --check`로 결정적으로 검증 가능하다. + +### 범위 결정 근거 + +- 포함: `LoadRatio` zero value가 Control Plane HTTP JSON에서 보존되도록 DTO tag를 수정한다. +- 포함: 기존 status handler success fixture의 second provider를 사용해 raw JSON에 `load_ratio: 0`이 존재하는지 확인한다. +- 제외: provider queue/status 계산, proto 원본/생성물, Client DTO/UI, Edge CLI 표시. 이번 Required와 직접 관련이 없다. + +## 구현 체크리스트 + +- [ ] `apps/control-plane/cmd/control-plane/http_views.go`에서 `providerSnapshotView.LoadRatio` JSON tag를 `json:"load_ratio"`로 바꿔 zero value도 직렬화한다. +- [ ] `apps/control-plane/cmd/control-plane/main_test.go`에서 second provider raw JSON assertion에 `load_ratio` key 존재 여부와 `float64(0)` 값을 추가한다. +- [ ] `go test ./apps/control-plane/cmd/control-plane -run TestEdgeRegistryHTTPHandlersGetLiveStatus -count=1`를 실행한다. +- [ ] `go test ./apps/control-plane/... -count=1`와 `git diff --check`를 실행한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +### [REVIEW_API-1] Zero Load Ratio JSON 보존 + +#### 문제 + +`ProviderSnapshot.LoadRatio`의 `0`은 idle provider의 정상 상태값이다. 현재 Control Plane HTTP DTO는 `json:"load_ratio,omitempty"`를 사용해 `0`을 JSON에서 생략한다. 이러면 SDD `S07`이 요구한 derived `load_ratio` 노출이 idle provider에서 깨지고, Client는 `0`과 필드 없음/unknown을 구분할 수 없다. + +#### 해결 방법 + +`LoadRatio` JSON tag에서 `omitempty`를 제거한다. 기존 테스트 fixture의 second provider는 `LoadRatio`를 명시하지 않아 zero value 케이스로 쓸 수 있으므로, raw JSON assertion에서 `rawP1["load_ratio"]`가 존재하고 `float64(0)`인지 검증한다. + +#### 수정 파일 및 체크리스트 + +- [ ] `apps/control-plane/cmd/control-plane/http_views.go`: `LoadRatio float32 `json:"load_ratio"`` +- [ ] `apps/control-plane/cmd/control-plane/main_test.go`: `rawP1` assertion에 `load_ratio` 존재/값 검증 추가. + +#### 중간 검증 + +```bash +go test ./apps/control-plane/cmd/control-plane -run TestEdgeRegistryHTTPHandlersGetLiveStatus -count=1 +``` + +기대 결과: status handler success/missing/upstream error subtest가 모두 통과하고, zero `load_ratio` raw JSON assertion도 통과한다. + +## 최종 검증 + +```bash +go test ./apps/control-plane/... -count=1 +git diff --check +``` + +기대 결과: Control Plane Go tests가 통과하고 공백 오류가 없다. Go test cache output은 허용하지 않는다. diff --git a/agent-task/archive/2026/06/m-provider-catalog-device-status/03_catalog_status_http/plan_local_G04_0.log b/agent-task/archive/2026/06/m-provider-catalog-device-status/03_catalog_status_http/plan_local_G04_0.log new file mode 100644 index 0000000..d58ffc8 --- /dev/null +++ b/agent-task/archive/2026/06/m-provider-catalog-device-status/03_catalog_status_http/plan_local_G04_0.log @@ -0,0 +1,293 @@ + + +# PLAN API - Control Plane Provider Status JSON + +## 이 파일을 읽는 구현 에이전트에게 + +이 plan의 구현은 active `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션 작성까지 끝나야 완료다. 코드 변경 후 검증 명령을 실행하고 실제 stdout/stderr를 review stub에 붙인 뒤 active 파일을 그대로 두고 리뷰 준비를 보고한다. 최종 판정, log rename, `complete.log`, archive 이동은 code-review 스킬 전용이다. + +구현 중 선택된 SDD 결정 또는 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 막으면 review stub의 `사용자 리뷰 요청` 섹션에 연결 대상, 차단 근거, 실행한 명령, 재개 조건을 기록하고 멈춘다. 직접 사용자에게 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 환경, secret, service 준비, 일반 범위 변경, 증거 공백은 사용자 리뷰 요청이 아니라 검증 결과 또는 후속 plan 대상으로 기록한다. + +## 배경 + +Edge wire/proto에는 `EdgeNodeSnapshot.provider_snapshots`와 `ProviderSnapshot`의 catalog/status 필드가 이미 있다. 그러나 Control Plane HTTP `/edges/{id}/status` 변환은 node의 config summary만 내보내고 provider snapshots를 버린다. 이 때문에 Edge-owned read-only provider status가 Control Plane과 Client 표면으로 전달되지 않아 `device-status`를 닫을 수 없다. + +## 사용자 리뷰 요청 흐름 + +사용자 리뷰 요청은 선택된 SDD 결정 또는 선택된 Milestone lock decision만 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 구현 중 직접 사용자 프롬프트는 금지하며, code-review가 정당성을 검증하고 실제 `USER_REVIEW.md` 작성 여부를 결정한다. + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/provider-catalog-device-status.md` +- Task ids: + - `device-status`: provider health, capacity, in-flight, derived load ratio, served models, queued 상태가 catalog/status 표면에 정리된다. +- Completion mode: check-on-pass + +## Spec Targets + +- SDD: `agent-roadmap/sdd/operational-observability-provider-management/provider-catalog-device-status/SDD.md` +- Acceptance scenarios: + - `S07`: task=`device-status`; evidence=`Control Plane HTTP status JSON exposes provider health, capacity, in_flight, derived load_ratio, served_models, queued without runtime/resource telemetry.` +- Completion mode: spec-check-on-pass + +## 분석 결과 + +### 읽은 파일 + +- `AGENTS.md` +- `agent-ops/rules/project/rules.md` +- `agent-ops/rules/common/rules-roadmap.md` +- `agent-ops/skills/common/router.md` +- `agent-ops/skills/common/plan/SKILL.md` +- `agent-ops/skills/common/update-roadmap/SKILL.md` +- `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` +- `agent-roadmap/current.md` +- `agent-roadmap/phase/operational-observability-provider-management/PHASE.md` +- `agent-roadmap/phase/operational-observability-provider-management/milestones/provider-catalog-device-status.md` +- `agent-roadmap/sdd/operational-observability-provider-management/provider-catalog-device-status/SDD.md` +- `agent-ops/rules/project/domain/control-plane/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/control-plane-smoke.md` +- `agent-test/local/platform-common-smoke.md` +- `proto/iop/control.proto` +- `proto/gen/iop/control.pb.go` +- `proto/iop/runtime.proto` +- `proto/gen/iop/runtime.pb.go` +- `apps/control-plane/cmd/control-plane/http_views.go` +- `apps/control-plane/cmd/control-plane/main_test.go` +- `apps/edge/internal/controlplane/connector.go` +- `apps/edge/internal/service/status_provider.go` +- `apps/edge/internal/service/model_queue.go` +- `apps/edge/internal/service/status_provider_test.go` + +### 테스트 환경 규칙 + +- test_env: `local`. +- `agent-test/local/rules.md`를 읽었다. Go quick check는 현재 local checkout에서 실행하고, Docker compose가 필요한 검증은 원격 runner가 필요하다는 규칙을 확인했다. +- 매칭 profile: `agent-test/local/control-plane-smoke.md`를 읽었다. 적용 명령은 `go test ./apps/control-plane/...`다. +- 참고 profile: `agent-test/local/platform-common-smoke.md`를 읽었다. 이번 plan은 proto 원본/생성물 변경 없이 기존 generated field를 소비하므로 `make proto`는 요구하지 않는다. +- HTTP lifecycle 자체를 새 endpoint로 확장하지 않고 기존 `/edges/{id}/status` JSON DTO를 보강한다. `/healthz`, `/readyz`, `make test-control-plane-edge-wire`는 smoke 후보지만 이 plan의 필수 최종 검증은 대상 Go 패키지 테스트로 둔다. + +### 테스트 커버리지 공백 + +- 현재 `TestEdgeRegistryHTTPHandlersGetLiveStatus`는 node config summary와 secret non-leak만 검증하고 provider snapshots JSON을 검증하지 않는다. +- `edgeStatusResponseViewFromProto`가 `EdgeNodeSnapshot.ProviderSnapshots`를 누락해도 현재 테스트가 실패하지 않는다. +- Edge internal status coverage는 `apps/edge/internal/service/status_provider_test.go`에서 provider `Capacity`, `InFlight`, `Queued`, `LoadRatio`까지 검증되어 있다. 이번 plan은 Control Plane JSON 변환의 회귀 테스트만 추가한다. + +### 심볼 참조 + +- renamed/removed symbols: none. +- 새 internal DTO 후보: `providerSnapshotView`. +- 변경 call site: `edgeStatusResponseViewFromProto` 내부 node mapping only. + +### 분할 판단 + +- split decision policy를 평가했다. +- 공유 task group: `agent-task/m-provider-catalog-device-status/`. +- `03_catalog_status_http`: Control Plane HTTP DTO/API test. dependency: none. +- `04+03_client_catalog_view`: Client DTO/UI 소비. dependency: `03_catalog_status_http` complete.log. +- 분할 이유: Control Plane JSON 계약이 Client UI의 입력 계약이므로 API surface를 먼저 닫고, Client는 그 JSON shape에 의존해 별도 검증한다. Go HTTP tests와 Flutter widget tests는 서로 다른 runner/리스크를 가진다. + +### 범위 결정 근거 + +- 포함: `apps/control-plane/cmd/control-plane/http_views.go`, `apps/control-plane/cmd/control-plane/main_test.go`. +- 제외: `proto/iop/*.proto`와 `proto/gen/**`. 이미 `EdgeNodeSnapshot.provider_snapshots`와 `ProviderSnapshot` generated getters가 있으므로 계약 변경이 아니다. +- 제외: Edge queue/status 계산. `apps/edge/internal/service/model_queue.go`와 status tests가 이미 provider stats를 만든다. +- 제외: Client DTO/UI. dependent plan `04+03_client_catalog_view`에서 처리한다. +- 제외: runtime control, resource telemetry, qualification report. SDD D05-D06과 Milestone 범위 제외에 따라 후속 범위다. + +### 빌드 등급 + +- lane/grade: `local-G04`. +- 근거: 단일 Go app HTTP DTO 변환과 대상 테스트 보강이다. 기존 proto/generated 타입이 있어 public wire contract 변경은 없고, `go test ./apps/control-plane/...`로 결정적으로 검증 가능하다. + +## 구현 체크리스트 + +- [ ] Control Plane HTTP status DTO에 provider snapshot read-only fields를 추가하고 `EdgeNodeSnapshot.ProviderSnapshots`를 JSON으로 전달한다. +- [ ] `/edges/{id}/status` 테스트에 provider snapshot fixture와 JSON assertion을 추가해 `health`, `capacity`, `in_flight`, `load_ratio`, `served_models`, `queued`, `category`, `lifecycle_capabilities`가 보존되는지 확인한다. +- [ ] raw config secret non-leak 검증이 provider snapshot 추가 후에도 유지되는지 확인한다. +- [ ] `go test ./apps/control-plane/...`를 실행한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +### [API-1] Provider Snapshot JSON DTO 추가 + +#### 문제 + +[apps/control-plane/cmd/control-plane/http_views.go](/config/workspace/iop/apps/control-plane/cmd/control-plane/http_views.go:138)에서 `edgeNodeSnapshotView`가 `NodeID`, `Alias`, `Label`, `Connected`, `Config`만 가진다. [apps/control-plane/cmd/control-plane/http_views.go](/config/workspace/iop/apps/control-plane/cmd/control-plane/http_views.go:191)의 `edgeStatusResponseViewFromProto`도 `n.GetProviderSnapshots()`를 읽지 않아 Edge가 보낸 provider status가 HTTP JSON에서 사라진다. + +현재 코드: + +```go +138 type edgeNodeSnapshotView struct { +139 NodeID string `json:"node_id"` +140 Alias string `json:"alias"` +141 Label string `json:"label"` +142 Connected bool `json:"connected"` +143 Config *nodeConfigSummaryView `json:"config,omitempty"` +144 } +... +191 func edgeStatusResponseViewFromProto(resp *iop.EdgeStatusResponse) edgeStatusResponseView { +192 nodes := make([]edgeNodeSnapshotView, 0, len(resp.GetNodes())) +193 for _, n := range resp.GetNodes() { +194 nodes = append(nodes, edgeNodeSnapshotView{ +195 NodeID: n.GetNodeId(), +196 Alias: n.GetAlias(), +197 Label: n.GetLabel(), +198 Connected: n.GetConnected(), +199 Config: newNodeConfigSummaryView(n.GetConfig()), +200 }) +201 } +``` + +#### 해결 방법 + +`providerSnapshotView`를 `http_views.go`에 추가하고 `edgeNodeSnapshotView`에 `ProviderSnapshots []providerSnapshotView 'json:"provider_snapshots,omitempty"'`를 둔다. 변환 helper는 proto getters만 사용하고 nil/empty는 nil을 반환한다. + +목표 코드: + +```go +type providerSnapshotView struct { + Adapter string `json:"adapter,omitempty"` + Status string `json:"status,omitempty"` + Capacity int32 `json:"capacity"` + InFlight int32 `json:"in_flight"` + Queued int32 `json:"queued"` + ID string `json:"id,omitempty"` + Type string `json:"type,omitempty"` + Category string `json:"category,omitempty"` + ServedModels []string `json:"served_models,omitempty"` + Health string `json:"health,omitempty"` + LoadRatio float32 `json:"load_ratio,omitempty"` + LifecycleCapabilities []string `json:"lifecycle_capabilities,omitempty"` +} + +func providerSnapshotViews(snaps []*iop.ProviderSnapshot) []providerSnapshotView { + if len(snaps) == 0 { + return nil + } + views := make([]providerSnapshotView, 0, len(snaps)) + for _, p := range snaps { + views = append(views, providerSnapshotView{ + Adapter: p.GetAdapter(), + Status: p.GetStatus(), + Capacity: p.GetCapacity(), + InFlight: p.GetInFlight(), + Queued: p.GetQueued(), + ID: p.GetId(), + Type: p.GetType(), + Category: p.GetCategory(), + ServedModels: append([]string(nil), p.GetServedModels()...), + Health: p.GetHealth(), + LoadRatio: p.GetLoadRatio(), + LifecycleCapabilities: append([]string(nil), p.GetLifecycleCapabilities()...), + }) + } + return views +} +``` + +`edgeStatusResponseViewFromProto`의 node append에는 `ProviderSnapshots: providerSnapshotViews(n.GetProviderSnapshots())`를 추가한다. + +#### 수정 파일 및 체크리스트 + +- [ ] `apps/control-plane/cmd/control-plane/http_views.go`: DTO struct와 helper 추가. +- [ ] `apps/control-plane/cmd/control-plane/http_views.go`: `edgeStatusResponseViewFromProto` node mapping에 provider snapshots 추가. +- [ ] `apps/control-plane/cmd/control-plane/main_test.go`: JSON shape assertion 추가. + +#### 테스트 작성 + +- 작성: `apps/control-plane/cmd/control-plane/main_test.go`의 `TestEdgeRegistryHTTPHandlersGetLiveStatus/Success`에 provider snapshot fixture와 typed/raw assertion을 추가한다. + +#### 중간 검증 + +```bash +go test ./apps/control-plane/cmd/control-plane -run TestEdgeRegistryHTTPHandlersGetLiveStatus -count=1 +``` + +기대 결과: provider snapshot JSON assertion과 기존 secret non-leak assertion이 모두 통과한다. + +### [API-2] Status JSON 회귀 테스트 보강 + +#### 문제 + +[apps/control-plane/cmd/control-plane/main_test.go](/config/workspace/iop/apps/control-plane/cmd/control-plane/main_test.go:444)의 fixture는 `EdgeStatusResponse.Nodes[0]`에 config만 넣고 provider snapshots를 넣지 않는다. 그래서 `provider_snapshots` 누락이 회귀로 잡히지 않는다. + +현재 fixture 일부: + +```go +444 return &iop.EdgeStatusResponse{ +445 RequestId: "req-123", +446 EdgeId: "edge-a", +447 EdgeName: "Edge A", +448 ObservedTimeUnixNano: connectedAt.UnixNano(), +449 Nodes: []*iop.EdgeNodeSnapshot{ +450 { +451 NodeId: "node-1", +452 Alias: "alpha", +453 Label: "node0", +454 Connected: true, +455 Config: &iop.NodeConfigPayload{ +``` + +#### 해결 방법 + +fixture에 provider snapshot을 추가하고 typed DTO와 raw JSON 모두 확인한다. raw assertion은 `provider_snapshots` 키가 있고 secret/config internals가 들어가지 않는다는 기존 검증과 공존해야 한다. + +목표 fixture: + +```go +ProviderSnapshots: []*iop.ProviderSnapshot{ + { + Adapter: "vllm-gpu", + Status: "available", + Capacity: 4, + InFlight: 1, + Queued: 2, + Id: "provider-vllm-primary", + Type: "vllm", + Category: "api", + ServedModels: []string{"nvidia/Qwen3.6-35B-A3B-NVFP4"}, + Health: "available", + LoadRatio: 0.25, + LifecycleCapabilities: []string{"list_models", "load_model"}, + }, +}, +``` + +Assert `node.ProviderSnapshots[0]` preserves all fields, and raw JSON has `provider_snapshots[0].load_ratio == 0.25`, `served_models[0]`, `lifecycle_capabilities[0]`. + +#### 수정 파일 및 체크리스트 + +- [ ] `apps/control-plane/cmd/control-plane/main_test.go`: fixture에 provider snapshot 추가. +- [ ] `apps/control-plane/cmd/control-plane/main_test.go`: typed DTO assertion 추가. +- [ ] `apps/control-plane/cmd/control-plane/main_test.go`: raw JSON key assertion 추가. + +#### 테스트 작성 + +- 작성: 위 test에 직접 추가한다. 별도 새 test보다 기존 status handler 성공 케이스에 붙이는 편이 HTTP view 계약을 한 곳에서 검증한다. + +#### 중간 검증 + +```bash +go test ./apps/control-plane/cmd/control-plane -run TestEdgeRegistryHTTPHandlersGetLiveStatus -count=1 +``` + +기대 결과: status handler 성공/누락/502 subtest가 모두 통과한다. + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `apps/control-plane/cmd/control-plane/http_views.go` | API-1 | +| `apps/control-plane/cmd/control-plane/main_test.go` | API-2 | + +## 최종 검증 + +```bash +go test ./apps/control-plane/... -count=1 +git diff --check +``` + +기대 결과: Go control-plane tests가 통과하고 공백 오류가 없다. Go test cache output은 허용하지 않는다. 모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/agent-task/m-provider-catalog-device-status/04+03_client_catalog_view/CODE_REVIEW-local-G05.md b/agent-task/m-provider-catalog-device-status/04+03_client_catalog_view/CODE_REVIEW-local-G05.md new file mode 100644 index 0000000..f932f8b --- /dev/null +++ b/agent-task/m-provider-catalog-device-status/04+03_client_catalog_view/CODE_REVIEW-local-G05.md @@ -0,0 +1,172 @@ + + +# Code Review Reference - API + +> **[IMPLEMENTING AGENT - READ FIRST] Filling in this file is the mandatory final step of implementation.** +> The task is NOT complete until every implementation-owned section below is filled in. +> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving. +> Fill implementation-owned sections, then stop with active files in place and report ready for review. +> If implementation is blocked by a selected SDD decision or selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with linked evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Environment/secret/service blockers, generic scope changes, repeated failures, and evidence gaps that a follow-up agent can close are normal follow-up issues, not user-review blockers by themselves. +> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation; record only SDD/Milestone lock decisions in `사용자 리뷰 요청` and stop for code-review. +> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume. +> Follow the ownership table at the bottom of this file for which sections you own. + +## 개요 + +date=2026-06-20 +task=m-provider-catalog-device-status/04+03_client_catalog_view, plan=0, tag=API + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/provider-catalog-device-status.md` +- Task ids: + - `ops-review`: Edge config와 Edge-owned read-only catalog/status를 기준으로 `iop-edge` CLI, Control Plane, Client가 어떤 정보를 소비하는지 확인할 수 있다. +- Completion mode: check-on-pass + +## Spec Targets + +- SDD: `agent-roadmap/sdd/operational-observability-provider-management/provider-catalog-device-status/SDD.md` +- Acceptance scenarios: + - `S10`: task=`ops-review`; evidence=`Edge CLI configured-provider summary, Control Plane provider status JSON, Client Nodes panel provider catalog/status rendering, and resolved user_review_0.log are present.` +- Completion mode: spec-check-on-pass + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. +리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다. + +1. 판정을 append한다. +2. `CODE_REVIEW-local-G05.md` -> `code_review_local_G05_0.log`, `PLAN-local-G05.md` -> `plan_local_G05_0.log`로 아카이브한다. +3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-provider-catalog-device-status/04+03_client_catalog_view/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. `USER_REVIEW.md`가 연결된 SDD/Milestone 결정으로 완료/PASS 해소되면 code-review가 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log` 작성 후 archive 이동한다. +4. PASS이고 task group이 `m-`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다. +5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [API-1] Client Provider Snapshot DTO 추가 | [ ] | +| [API-2] Nodes Panel Provider Catalog 표시 | [ ] | +| [API-3] Ops Review Evidence 연결 | [ ] | + +## 구현 체크리스트 + +- [ ] 선행 task `agent-task/m-provider-catalog-device-status/03_catalog_status_http/complete.log` 또는 matching archive complete.log가 존재하는지 확인하고, 없으면 구현하지 말고 dependency missing으로 기록한다. +- [ ] Client HTTP DTO에 `ProviderSnapshotView`와 `EdgeNodeSnapshotView.providerSnapshots` parsing을 추가한다. +- [ ] Nodes panel에 read-only Provider Catalog/Status 섹션을 추가해 provider id/type/category, health/status, capacity, in-flight, queued, load ratio, served models, lifecycle capabilities를 표시한다. +- [ ] Fake status repository와 widget test fixture에 provider snapshot을 추가하고 Nodes panel test가 provider catalog/status 표시를 검증하도록 보강한다. +- [ ] `cd apps/client && flutter test`를 실행한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. +> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다. + +- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다. +- [ ] active `CODE_REVIEW-*-G??.md`를 `code_review_local_G05_0.log`로 아카이브한다. +- [ ] active `PLAN-*-G??.md`를 `plan_local_G05_0.log`로 아카이브한다. +- [ ] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다. +- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. +- [ ] PASS이면 active task 디렉터리 `agent-task/m-provider-catalog-device-status/04+03_client_catalog_view/`를 `agent-task/archive/YYYY/MM/m-provider-catalog-device-status/04+03_client_catalog_view/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. +- [ ] PASS이고 task group이 `m-`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다. +- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-provider-catalog-device-status/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. +- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-local-G05.md`와 `CODE_REVIEW-local-G05.md`를 작성하고 `complete.log`를 작성하지 않는다. +- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다. +- [ ] USER_REVIEW가 연결된 SDD/Milestone 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다. + +## 계획 대비 변경 사항 + +_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._ + +## 주요 설계 결정 + +_구현 에이전트가 주요 설계 결정 사항을 기록한다._ + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 SDD 결정 또는 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 연결 대상: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- 구현 전 `03_catalog_status_http` complete.log가 확인되었는지 본다. +- DTO parsing이 `capacity`, `in_flight`, `queued`, `load_ratio` numeric 값을 안전하게 처리하는지 확인한다. +- Nodes panel이 provider catalog/status를 표시하되 runtime control UI를 추가하지 않았는지 확인한다. +- `ops-review` evidence에 Edge CLI, Control Plane, Client, `user_review_0.log` 연결이 남았는지 확인한다. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +필수 규칙: +- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다. +- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다. +- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다. +- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다. +- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다. + +### API-1 중간 검증 + +```text +$ cd apps/client && flutter test test/widget_test.dart +(output) +``` + +### API-2 중간 검증 + +```text +$ cd apps/client && flutter test test/widget_test.dart +(output) +``` + +### API-3 중간 검증 + +```text +$ test -f agent-task/m-provider-catalog-device-status/03_catalog_status_http/complete.log || find agent-task/archive -path '*/m-provider-catalog-device-status/03_catalog_status_http/complete.log' -print -quit +(output) +``` + +### 최종 검증 + +```text +$ cd apps/client && flutter test +(output) + +$ git diff --check +(output) +``` + +--- + +> **[IMPLEMENTING AGENT - BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?** +> If anything is blank, go back and fill it in before saving this file. +> Leave review-agent-only sections unchanged. + +## 섹션 소유권 + +| Section | Owner | Note | +|---------|-------|------| +| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute these | +| Roadmap Targets | Fixed at stub creation from plan when present | Implementing agent must not modify | +| Spec Targets | Fixed at stub creation from plan when present | Implementing agent must not modify | +| 구현 항목별 완료 여부 | Implementing agent | Check `[ ]` to `[x]` only | +| 구현 체크리스트 | Implementing agent | Check `[ ]` to `[x]` only | +| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify | +| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholder text with actual content | +| 사용자 리뷰 요청 | Implementing agent | Keep `상태: 없음` unless a selected SDD/Milestone lock decision blocks implementation | +| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Review focus | +| 검증 결과 | Implementing agent | Paste actual command output | diff --git a/agent-task/m-provider-catalog-device-status/04+03_client_catalog_view/PLAN-local-G05.md b/agent-task/m-provider-catalog-device-status/04+03_client_catalog_view/PLAN-local-G05.md new file mode 100644 index 0000000..50767b2 --- /dev/null +++ b/agent-task/m-provider-catalog-device-status/04+03_client_catalog_view/PLAN-local-G05.md @@ -0,0 +1,335 @@ + + +# PLAN API - Client Provider Catalog View + +## 이 파일을 읽는 구현 에이전트에게 + +이 plan은 `04+03_client_catalog_view` dependent split task다. 같은 task group의 `03_catalog_status_http`가 `complete.log`를 만들기 전에는 구현을 시작하지 않는다. 구현이 끝나도 active `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션 작성까지 끝나야 완료다. 검증 명령의 실제 stdout/stderr를 review stub에 붙이고 active 파일을 그대로 둔 뒤 리뷰 준비를 보고한다. 최종 판정, log rename, `complete.log`, archive 이동은 code-review 스킬 전용이다. + +구현 중 선택된 SDD 결정 또는 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 막으면 review stub의 `사용자 리뷰 요청` 섹션에 연결 대상, 차단 근거, 실행한 명령, 재개 조건을 기록하고 멈춘다. 직접 사용자에게 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 환경, secret, service 준비, 일반 범위 변경, 증거 공백은 사용자 리뷰 요청이 아니라 검증 결과 또는 후속 plan 대상으로 기록한다. + +## 배경 + +Control Plane Client는 `/edges/{id}/status` JSON을 소비하지만 `EdgeNodeSnapshotView`가 provider snapshots를 파싱하지 않는다. Nodes panel도 node config adapters만 보여 주므로 운영자가 provider category, status, load, served models, lifecycle capability를 Client에서 확인할 수 없다. 03번 plan이 Control Plane HTTP JSON 계약을 닫으면, 이 plan은 Client DTO와 Nodes view에서 같은 read-only catalog/status를 표시한다. + +## 사용자 리뷰 요청 흐름 + +사용자 리뷰 요청은 선택된 SDD 결정 또는 선택된 Milestone lock decision만 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 구현 중 직접 사용자 프롬프트는 금지하며, code-review가 정당성을 검증하고 실제 `USER_REVIEW.md` 작성 여부를 결정한다. + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/provider-catalog-device-status.md` +- Task ids: + - `ops-review`: Edge config와 Edge-owned read-only catalog/status를 기준으로 `iop-edge` CLI, Control Plane, Client가 어떤 정보를 소비하는지 확인할 수 있다. +- Completion mode: check-on-pass + +## Spec Targets + +- SDD: `agent-roadmap/sdd/operational-observability-provider-management/provider-catalog-device-status/SDD.md` +- Acceptance scenarios: + - `S10`: task=`ops-review`; evidence=`Edge CLI configured-provider summary, Control Plane provider status JSON, Client Nodes panel provider catalog/status rendering, and resolved user_review_0.log are present.` +- Completion mode: spec-check-on-pass + +## 분석 결과 + +### 읽은 파일 + +- `AGENTS.md` +- `agent-ops/rules/project/rules.md` +- `agent-ops/rules/common/rules-roadmap.md` +- `agent-ops/skills/common/router.md` +- `agent-ops/skills/common/plan/SKILL.md` +- `agent-ops/skills/common/update-roadmap/SKILL.md` +- `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` +- `agent-roadmap/current.md` +- `agent-roadmap/phase/operational-observability-provider-management/PHASE.md` +- `agent-roadmap/phase/operational-observability-provider-management/milestones/provider-catalog-device-status.md` +- `agent-roadmap/sdd/operational-observability-provider-management/provider-catalog-device-status/SDD.md` +- `agent-ops/rules/project/domain/client/rules.md` +- `agent-ops/rules/project/domain/control-plane/rules.md` +- `agent-ops/rules/project/domain/testing/rules.md` +- `agent-test/local/rules.md` +- `agent-test/local/client-smoke.md` +- `agent-test/local/control-plane-smoke.md` +- `apps/client/lib/control_plane_status_client.dart` +- `apps/client/lib/control_plane_status_dto.dart` +- `apps/client/lib/control_plane_status_repository.dart` +- `apps/client/lib/widgets/nodes_panel.dart` +- `apps/client/lib/widgets/edges_panel.dart` +- `apps/client/test/widget_test.dart` +- `apps/client/test/control_plane_status_controller_test.dart` +- `apps/control-plane/cmd/control-plane/http_views.go` +- `proto/iop/control.proto` +- `proto/iop/runtime.proto` + +### 테스트 환경 규칙 + +- test_env: `local`. +- `agent-test/local/rules.md`를 읽었다. Flutter client 포함 검증은 원격 runner 또는 동기화된 Flutter 환경 기준이며, 현재 checkout의 변경분이 원격에 없으면 원격 검증 완료로 보지 않는다고 확인했다. +- 매칭 profile: `agent-test/local/client-smoke.md`를 읽었다. 필수 명령은 `cd apps/client && flutter test`다. +- 관련 upstream profile: `agent-test/local/control-plane-smoke.md`를 읽었다. 이 plan은 Control Plane 코드를 수정하지 않으며 03번 plan의 complete.log를 선행 조건으로 둔다. +- Docker compose, web dev server, analyzer는 이번 DTO/widget 범위의 필수 검증이 아니다. Flutter analyzer 영향이 확인되면 구현 에이전트가 `cd apps/client && flutter analyze --no-fatal-infos`를 추가 실행하고 review stub에 기록한다. + +### 테스트 커버리지 공백 + +- `EdgeNodeSnapshotView.fromJson`은 `provider_snapshots`를 파싱하지 않는다. HTTP repository가 JSON을 받아도 DTO에서 버린다. +- `NodesPanel`은 node config adapters만 표시한다. provider status, category, served models, load ratio, lifecycle capabilities가 UI에 없다. +- `FakeControlPlaneStatusRepository.fetchEdgeStatus` fixture와 `Client App opens Nodes panel...` widget test는 provider catalog/status가 표시되어야 한다는 회귀 조건을 갖고 있지 않다. + +### 심볼 참조 + +- renamed/removed symbols: none. +- 새 DTO 후보: `ProviderSnapshotView`. +- 새 field 후보: `EdgeNodeSnapshotView.providerSnapshots`. +- 변경 call sites: `EdgeNodeSnapshotView` constructor usages in `apps/client/test/widget_test.dart` and `apps/client/test/control_plane_status_controller_test.dart` may need `providerSnapshots: []` only if constructor is kept `required`; prefer optional default list to minimize call-site churn. + +### 분할 판단 + +- split decision policy를 평가했다. +- 공유 task group: `agent-task/m-provider-catalog-device-status/`. +- predecessor: `03_catalog_status_http` - 현재 active plan으로 존재하며 complete.log는 아직 없음. +- current task: `04+03_client_catalog_view` - `03` complete.log가 필요하다. +- 분할 이유: Client DTO/UI는 Control Plane HTTP JSON shape에 의존한다. Go API 변환과 Flutter widget 검증은 서로 다른 ownership/test runner를 가지므로 별도 review unit으로 둔다. + +### 범위 결정 근거 + +- 포함: `apps/client/lib/control_plane_status_dto.dart`, `apps/client/lib/widgets/nodes_panel.dart`, `apps/client/test/widget_test.dart`. 필요 시 DTO parse 단위 test를 기존 client test 파일에 추가할 수 있다. +- 제외: `apps/control-plane/**`. 03번 plan의 PASS가 선행 조건이다. +- 제외: generated Dart proto. Client는 Control Plane HTTP JSON DTO를 소비하므로 proto binding 수정이 아니다. +- 제외: Edge CLI. 작은 직접 작업으로 `iop-edge nodes list` configured provider summary가 추가되어 있다. +- 제외: runtime enable/disable, drain, fallback 우선순위, capacity override. SDD D06과 Milestone 범위 제외에 따라 후속 범위다. + +### 빌드 등급 + +- lane/grade: `local-G05`. +- 근거: Flutter DTO와 widget 표시 변경이며 UI test로 검증 가능하다. 다만 Client runner/Flutter SDK 상태와 layout 회귀 위험이 있어 G04보다 한 단계 높였다. + +## 구현 체크리스트 + +- [ ] 선행 task `agent-task/m-provider-catalog-device-status/03_catalog_status_http/complete.log` 또는 matching archive complete.log가 존재하는지 확인하고, 없으면 구현하지 말고 dependency missing으로 기록한다. +- [ ] Client HTTP DTO에 `ProviderSnapshotView`와 `EdgeNodeSnapshotView.providerSnapshots` parsing을 추가한다. +- [ ] Nodes panel에 read-only Provider Catalog/Status 섹션을 추가해 provider id/type/category, health/status, capacity, in-flight, queued, load ratio, served models, lifecycle capabilities를 표시한다. +- [ ] Fake status repository와 widget test fixture에 provider snapshot을 추가하고 Nodes panel test가 provider catalog/status 표시를 검증하도록 보강한다. +- [ ] `cd apps/client && flutter test`를 실행한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 의존 관계 및 구현 순서 + +- 이 subtask dir 이름 `04+03_client_catalog_view`는 predecessor index `03`만 요구한다. +- 구현 시작 전 다음 중 하나를 확인한다. + - active: `agent-task/m-provider-catalog-device-status/03_catalog_status_http/complete.log` + - archive: `agent-task/archive/*/*/m-provider-catalog-device-status/03_catalog_status_http/complete.log` +- 현재 계획 작성 시점에는 predecessor complete.log가 없다. 구현 에이전트는 03 PASS 이후 진행한다. + +### [API-1] Client Provider Snapshot DTO 추가 + +#### 문제 + +[apps/client/lib/control_plane_status_dto.dart](/config/workspace/iop/apps/client/lib/control_plane_status_dto.dart:65)의 `EdgeNodeSnapshotView`는 provider snapshots field가 없다. [apps/client/lib/control_plane_status_dto.dart](/config/workspace/iop/apps/client/lib/control_plane_status_dto.dart:80)의 `fromJson`도 `provider_snapshots`를 읽지 않는다. + +현재 코드: + +```dart +65 class EdgeNodeSnapshotView { +66 final String nodeId; +67 final String alias; +68 final String label; +69 final bool connected; +70 final NodeConfigSummaryView? config; +... +80 factory EdgeNodeSnapshotView.fromJson(Map json) { +81 return EdgeNodeSnapshotView( +82 nodeId: json['node_id'] as String? ?? '', +83 alias: json['alias'] as String? ?? '', +84 label: json['label'] as String? ?? '', +85 connected: json['connected'] as bool? ?? false, +86 config: json['config'] != null +87 ? NodeConfigSummaryView.fromJson(json['config'] as Map) +88 : null, +89 ); +90 } +91 } +``` + +#### 해결 방법 + +`ProviderSnapshotView` DTO를 추가한다. Numeric JSON은 Go에서 `int`/`float`로 올 수 있으므로 `capacity`, `in_flight`, `queued`는 `as num?` 후 `toInt()`, `load_ratio`는 `toDouble()`로 파싱한다. `EdgeNodeSnapshotView` 생성자에는 `List providerSnapshots = const []` default를 둬 기존 fixture churn을 줄인다. + +목표 코드: + +```dart +class ProviderSnapshotView { + final String id; + final String adapter; + final String type; + final String category; + final String status; + final String health; + final int capacity; + final int inFlight; + final int queued; + final double loadRatio; + final List servedModels; + final List lifecycleCapabilities; + + ProviderSnapshotView({ + required this.id, + required this.adapter, + required this.type, + required this.category, + required this.status, + required this.health, + required this.capacity, + required this.inFlight, + required this.queued, + required this.loadRatio, + required this.servedModels, + required this.lifecycleCapabilities, + }); + + factory ProviderSnapshotView.fromJson(Map json) { + return ProviderSnapshotView( + id: json['id'] as String? ?? '', + adapter: json['adapter'] as String? ?? '', + type: json['type'] as String? ?? '', + category: json['category'] as String? ?? '', + status: json['status'] as String? ?? '', + health: json['health'] as String? ?? '', + capacity: (json['capacity'] as num?)?.toInt() ?? 0, + inFlight: (json['in_flight'] as num?)?.toInt() ?? 0, + queued: (json['queued'] as num?)?.toInt() ?? 0, + loadRatio: (json['load_ratio'] as num?)?.toDouble() ?? 0, + servedModels: List.from(json['served_models'] ?? []), + lifecycleCapabilities: List.from(json['lifecycle_capabilities'] ?? []), + ); + } +} +``` + +#### 수정 파일 및 체크리스트 + +- [ ] `apps/client/lib/control_plane_status_dto.dart`: DTO 추가. +- [ ] `apps/client/lib/control_plane_status_dto.dart`: `EdgeNodeSnapshotView.providerSnapshots` 추가 및 JSON parsing. +- [ ] `apps/client/test/widget_test.dart` 또는 기존 client unit test: JSON parse assertion 추가 또는 widget fixture에서 파싱 경로 검증. + +#### 테스트 작성 + +- 작성: `apps/client/test/widget_test.dart`의 fake repository fixture에 provider snapshots를 추가한다. 별도 pure DTO test가 더 간결하면 같은 test file 또는 `control_plane_status_controller_test.dart`에 추가해도 된다. + +#### 중간 검증 + +```bash +cd apps/client && flutter test test/widget_test.dart +``` + +기대 결과: 기존 app/widget tests와 provider DTO 표시 assertion이 통과한다. + +### [API-2] Nodes Panel Provider Catalog 표시 + +#### 문제 + +[apps/client/lib/widgets/nodes_panel.dart](/config/workspace/iop/apps/client/lib/widgets/nodes_panel.dart:211)은 config가 있을 때 concurrency와 adapters만 표시한다. provider snapshots가 추가되어도 UI에서 볼 수 없다. + +현재 코드: + +```dart +211 if (node.config != null) ...[ +212 const SizedBox(height: 16), +213 const Divider(color: Color(0xFF334155)), +214 const SizedBox(height: 12), +215 Row( +216 children: [ +217 const Icon(Icons.bolt, size: 16, color: Color(0xFF6366F1)), +218 const SizedBox(width: 6), +219 Text( +220 'Concurrency Limit: ${node.config!.concurrency}', +... +226 const Text( +227 'Adapters', +... +234 Wrap( +235 spacing: 8, +236 runSpacing: 8, +237 children: node.config!.adapters.map((adapter) { +``` + +#### 해결 방법 + +Node card 안에 `Provider Catalog` 섹션을 추가한다. Cards 안에 별도 `Card`를 중첩하지 말고 기존 dark panel 안에서 `Column`, `Wrap`, `Container`로 compact rows를 만든다. 표시 필드는 다음을 포함한다. + +- provider label: `id`가 있으면 id, 없으면 adapter. +- type/category: `type`과 `category`. +- state: `health` 우선, 없으면 `status`. +- load: `inFlight/capacity`, `queued`, `loadRatio`. +- served models: comma-separated first line, 길면 wrap. +- lifecycle capabilities: coarse flags만 표시. + +UI text는 기존 Nodes panel 스타일에 맞춰 작고 조밀하게 둔다. 새 marketing/설명 문구는 추가하지 않는다. + +#### 수정 파일 및 체크리스트 + +- [ ] `apps/client/lib/widgets/nodes_panel.dart`: provider section helper 추가. +- [ ] `apps/client/lib/widgets/nodes_panel.dart`: node card에 provider snapshots가 있을 때만 section 렌더링. +- [ ] `apps/client/test/widget_test.dart`: `Provider Catalog`, provider id/type/category/status/load/models/lifecycle text assertion 추가. + +#### 테스트 작성 + +- 작성: `Client App opens Nodes panel and displays active Nodes and configurations`에 provider display assertion을 추가한다. + +#### 중간 검증 + +```bash +cd apps/client && flutter test test/widget_test.dart +``` + +기대 결과: Nodes panel test가 provider catalog/status 표시까지 검증한다. + +### [API-3] Ops Review Evidence 연결 + +#### 문제 + +Milestone `ops-review`는 `iop-edge` CLI, Control Plane, Client가 어떤 정보를 소비하는지 확인할 수 있어야 한다. 작은 직접 작업으로 `iop-edge nodes list` configured provider summary는 추가됐지만, Client plan이 PASS될 때 전체 소비 경로 근거가 review/complete log에 남아야 한다. + +#### 해결 방법 + +review stub의 `주요 설계 결정` 또는 `검증 결과`에 다음 증거를 남긴다. + +- Edge CLI: `apps/edge/internal/edgecmd/nodes.go`가 configured provider category/type/models/capacity/lifecycle summary를 출력하고 `TestNodesListPrintsConfiguredProviders`가 통과했다. +- Control Plane: predecessor 03 complete.log가 `/edges/{id}/status` provider snapshots JSON을 검증했다. +- Client: 이 plan의 widget test가 provider catalog/status 표시를 검증했다. +- SDD review: `agent-roadmap/sdd/operational-observability-provider-management/provider-catalog-device-status/user_review_0.log`가 D01-D06 해결 이력으로 존재한다. + +#### 수정 파일 및 체크리스트 + +- [ ] `CODE_REVIEW-local-G05.md`: 구현 후 주요 설계 결정 또는 검증 결과에 위 evidence 연결을 기록한다. +- [ ] source code는 API-1/API-2 범위를 넘기지 않는다. + +#### 테스트 작성 + +- 별도 테스트 없음. API-1/API-2 테스트와 predecessor 03 complete.log가 evidence다. + +#### 중간 검증 + +```bash +test -f agent-task/m-provider-catalog-device-status/03_catalog_status_http/complete.log || find agent-task/archive -path '*/m-provider-catalog-device-status/03_catalog_status_http/complete.log' -print -quit +``` + +기대 결과: 03 predecessor complete.log 경로가 출력된다. + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `apps/client/lib/control_plane_status_dto.dart` | API-1 | +| `apps/client/lib/widgets/nodes_panel.dart` | API-2 | +| `apps/client/test/widget_test.dart` | API-1, API-2 | +| `agent-task/m-provider-catalog-device-status/04+03_client_catalog_view/CODE_REVIEW-local-G05.md` | API-3 | + +## 최종 검증 + +```bash +cd apps/client && flutter test +git diff --check +``` + +기대 결과: Flutter tests가 통과하고 공백 오류가 없다. Go test cache output은 해당 없음. 모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/apps/control-plane/cmd/control-plane/http_views.go b/apps/control-plane/cmd/control-plane/http_views.go index 19ee0e1..ed892f6 100644 --- a/apps/control-plane/cmd/control-plane/http_views.go +++ b/apps/control-plane/cmd/control-plane/http_views.go @@ -136,11 +136,27 @@ type fleetCommandResponse struct { } type edgeNodeSnapshotView struct { - NodeID string `json:"node_id"` - Alias string `json:"alias"` - Label string `json:"label"` - Connected bool `json:"connected"` - Config *nodeConfigSummaryView `json:"config,omitempty"` + NodeID string `json:"node_id"` + Alias string `json:"alias"` + Label string `json:"label"` + Connected bool `json:"connected"` + Config *nodeConfigSummaryView `json:"config,omitempty"` + ProviderSnapshots []providerSnapshotView `json:"provider_snapshots,omitempty"` +} + +type providerSnapshotView struct { + Adapter string `json:"adapter,omitempty"` + Status string `json:"status,omitempty"` + Capacity int32 `json:"capacity"` + InFlight int32 `json:"in_flight"` + Queued int32 `json:"queued"` + ID string `json:"id,omitempty"` + Type string `json:"type,omitempty"` + Category string `json:"category,omitempty"` + ServedModels []string `json:"served_models,omitempty"` + Health string `json:"health,omitempty"` + LoadRatio float32 `json:"load_ratio"` + LifecycleCapabilities []string `json:"lifecycle_capabilities,omitempty"` } type nodeConfigSummaryView struct { @@ -176,6 +192,30 @@ func newNodeConfigSummaryView(payload *iop.NodeConfigPayload) *nodeConfigSummary } } +func providerSnapshotViews(snaps []*iop.ProviderSnapshot) []providerSnapshotView { + if len(snaps) == 0 { + return nil + } + views := make([]providerSnapshotView, 0, len(snaps)) + for _, p := range snaps { + views = append(views, providerSnapshotView{ + Adapter: p.GetAdapter(), + Status: p.GetStatus(), + Capacity: p.GetCapacity(), + InFlight: p.GetInFlight(), + Queued: p.GetQueued(), + ID: p.GetId(), + Type: p.GetType(), + Category: p.GetCategory(), + ServedModels: append([]string(nil), p.GetServedModels()...), + Health: p.GetHealth(), + LoadRatio: p.GetLoadRatio(), + LifecycleCapabilities: append([]string(nil), p.GetLifecycleCapabilities()...), + }) + } + return views +} + func edgeRegistryViewFromState(state wire.EdgeConnectionState) edgeRegistryView { return edgeRegistryView{ EdgeID: state.EdgeID, @@ -192,11 +232,12 @@ func edgeStatusResponseViewFromProto(resp *iop.EdgeStatusResponse) edgeStatusRes nodes := make([]edgeNodeSnapshotView, 0, len(resp.GetNodes())) for _, n := range resp.GetNodes() { nodes = append(nodes, edgeNodeSnapshotView{ - NodeID: n.GetNodeId(), - Alias: n.GetAlias(), - Label: n.GetLabel(), - Connected: n.GetConnected(), - Config: newNodeConfigSummaryView(n.GetConfig()), + NodeID: n.GetNodeId(), + Alias: n.GetAlias(), + Label: n.GetLabel(), + Connected: n.GetConnected(), + Config: newNodeConfigSummaryView(n.GetConfig()), + ProviderSnapshots: providerSnapshotViews(n.GetProviderSnapshots()), }) } return edgeStatusResponseView{ diff --git a/apps/control-plane/cmd/control-plane/main_test.go b/apps/control-plane/cmd/control-plane/main_test.go index b6e6f21..f9614ea 100644 --- a/apps/control-plane/cmd/control-plane/main_test.go +++ b/apps/control-plane/cmd/control-plane/main_test.go @@ -464,6 +464,32 @@ func TestEdgeRegistryHTTPHandlersGetLiveStatus(t *testing.T) { Concurrency: 4, }, }, + ProviderSnapshots: []*iop.ProviderSnapshot{ + { + Adapter: "vllm-gpu", + Status: "available", + Capacity: 4, + InFlight: 1, + Queued: 2, + Id: "provider-vllm-primary", + Type: "vllm", + Category: "api", + ServedModels: []string{"nvidia/Qwen3.6-35B-A3B-NVFP4"}, + Health: "available", + LoadRatio: 0.25, + LifecycleCapabilities: []string{"list_models", "load_model"}, + }, + { + Adapter: "ollama-cpu", + Status: "backlog", + Capacity: 2, + InFlight: 2, + Queued: 5, + Id: "provider-ollama-default", + Type: "ollama", + Health: "unavailable", + }, + }, }, }, }, nil @@ -505,6 +531,41 @@ func TestEdgeRegistryHTTPHandlersGetLiveStatus(t *testing.T) { t.Fatalf("unexpected adapter summary: %+v", adapter) } + // Provider snapshots assertions + if len(node.ProviderSnapshots) != 2 { + t.Fatalf("expected 2 provider snapshots, got %d", len(node.ProviderSnapshots)) + } + + // First provider snapshot (vllm-gpu) + p0 := node.ProviderSnapshots[0] + if p0.Adapter != "vllm-gpu" || p0.Status != "available" || p0.Capacity != 4 { + t.Fatalf("unexpected provider snapshot[0]: %+v", p0) + } + if p0.InFlight != 1 || p0.Queued != 2 { + t.Fatalf("unexpected in_flight/queued: in_flight=%d queued=%d", p0.InFlight, p0.Queued) + } + if p0.ID != "provider-vllm-primary" || p0.Type != "vllm" || p0.Category != "api" { + t.Fatalf("unexpected id/type/category: id=%q type=%q category=%q", p0.ID, p0.Type, p0.Category) + } + if len(p0.ServedModels) != 1 || p0.ServedModels[0] != "nvidia/Qwen3.6-35B-A3B-NVFP4" { + t.Fatalf("unexpected served_models: %+v", p0.ServedModels) + } + if p0.Health != "available" || p0.LoadRatio != 0.25 { + t.Fatalf("unexpected health/load_ratio: health=%q load_ratio=%f", p0.Health, p0.LoadRatio) + } + if len(p0.LifecycleCapabilities) != 2 || p0.LifecycleCapabilities[0] != "list_models" || p0.LifecycleCapabilities[1] != "load_model" { + t.Fatalf("unexpected lifecycle_capabilities: %+v", p0.LifecycleCapabilities) + } + + // Second provider snapshot (ollama-cpu) + p1 := node.ProviderSnapshots[1] + if p1.Adapter != "ollama-cpu" || p1.Status != "backlog" || p1.Health != "unavailable" { + t.Fatalf("unexpected provider snapshot[1]: %+v", p1) + } + if p1.Capacity != 2 || p1.InFlight != 2 || p1.Queued != 5 { + t.Fatalf("unexpected capacity/in_flight/queued for ollama: capacity=%d in_flight=%d queued=%d", p1.Capacity, p1.InFlight, p1.Queued) + } + bodyStr := resp.Body.String() if strings.Contains(bodyStr, "super-secret-token") || strings.Contains(bodyStr, "secret_key") || strings.Contains(bodyStr, "settings") { t.Fatalf("leaked raw config settings in response: %s", bodyStr) @@ -536,6 +597,64 @@ func TestEdgeRegistryHTTPHandlersGetLiveStatus(t *testing.T) { t.Fatalf("config summary missing key %q: %#v", required, rawConfig) } } + + // Provider snapshots raw JSON assertions + rawProviderSnapshots, ok := rawNode["provider_snapshots"].([]any) + if !ok { + t.Fatalf("provider_snapshots key missing or not an array: %#v", rawNode) + } + if len(rawProviderSnapshots) != 2 { + t.Fatalf("expected 2 provider_snapshots in raw JSON, got %d", len(rawProviderSnapshots)) + } + + rawP0, ok := rawProviderSnapshots[0].(map[string]any) + if !ok { + t.Fatalf("provider_snapshots[0] is not an object: %#v", rawProviderSnapshots[0]) + } + if rawP0["adapter"] != "vllm-gpu" { + t.Fatalf("provider_snapshots[0].adapter: got %v", rawP0["adapter"]) + } + if rawP0["capacity"] != float64(4) { + t.Fatalf("provider_snapshots[0].capacity: got %v", rawP0["capacity"]) + } + if rawP0["load_ratio"] != float64(0.25) { + t.Fatalf("provider_snapshots[0].load_ratio: got %v", rawP0["load_ratio"]) + } + servedModels, ok := rawP0["served_models"].([]any) + if !ok || len(servedModels) != 1 { + t.Fatalf("provider_snapshots[0].served_models: got %#v", rawP0["served_models"]) + } + if servedModels[0] != "nvidia/Qwen3.6-35B-A3B-NVFP4" { + t.Fatalf("provider_snapshots[0].served_models[0]: got %v", servedModels[0]) + } + lifecycleCaps, ok := rawP0["lifecycle_capabilities"].([]any) + if !ok || len(lifecycleCaps) != 2 { + t.Fatalf("provider_snapshots[0].lifecycle_capabilities: got %#v", rawP0["lifecycle_capabilities"]) + } + if lifecycleCaps[0] != "list_models" || lifecycleCaps[1] != "load_model" { + t.Fatalf("provider_snapshots[0].lifecycle_capabilities: got %v", lifecycleCaps) + } + + // Second provider snapshot raw check + rawP1, ok := rawProviderSnapshots[1].(map[string]any) + if !ok { + t.Fatalf("provider_snapshots[1] is not an object: %#v", rawProviderSnapshots[1]) + } + if rawP1["adapter"] != "ollama-cpu" || rawP1["health"] != "unavailable" { + t.Fatalf("provider_snapshots[1] adapter/health: got %#v", rawP1) + } + // Zero load_ratio must be present in raw JSON (omitempty 제거 검증) + if rawP1["load_ratio"] != float64(0) { + t.Fatalf("provider_snapshots[1].load_ratio: expected 0 for idle provider, got %v", rawP1["load_ratio"]) + } + + // Ensure raw JSON does not leak config internals into provider_snapshots + providerSnapshotsStr, _ := json.Marshal(rawP0) + for _, forbidden := range []string{"runtime", "settings", "secret"} { + if strings.Contains(string(providerSnapshotsStr), forbidden) { + t.Fatalf("provider_snapshot leaked raw config key %q: %s", forbidden, providerSnapshotsStr) + } + } }) t.Run("MissingEdge", func(t *testing.T) { diff --git a/apps/edge/internal/edgecmd/edgecmd_test.go b/apps/edge/internal/edgecmd/edgecmd_test.go index 16c31c2..6b64111 100644 --- a/apps/edge/internal/edgecmd/edgecmd_test.go +++ b/apps/edge/internal/edgecmd/edgecmd_test.go @@ -179,6 +179,56 @@ func TestNodeRegisterUpsertsYAMLAndOutputsBootstrap(t *testing.T) { } } +func TestNodesListPrintsConfiguredProviders(t *testing.T) { + dir := t.TempDir() + cfgPath := filepath.Join(dir, "edge.yaml") + yaml := ` +server: + listen: "0.0.0.0:9090" +nodes: + - id: "node-gpu-01" + alias: "gpu-node" + token: "token-1" + providers: + - id: "vllm-gpu" + type: "vllm" + category: "api" + adapter: "vllm-gpu" + models: + - "nvidia/Qwen3.6-35B-A3B-NVFP4" + capacity: 4 + lifecycle_capabilities: + - "list_models" + - "load_model" +` + if err := os.WriteFile(cfgPath, []byte(yaml), 0o600); err != nil { + t.Fatalf("write %s: %v", cfgPath, err) + } + + root := NewRoot(Options{}) + var out bytes.Buffer + root.SetOut(&out) + root.SetErr(&out) + root.SetArgs([]string{"--config", cfgPath, "nodes", "list"}) + if err := root.Execute(); err != nil { + t.Fatalf("execute nodes list: %v\n%s", err, out.String()) + } + + got := out.String() + for _, want := range []string{ + "node-gpu-01", + "providers: vllm-gpu", + "type=vllm/api", + "models=nvidia/Qwen3.6-35B-A3B-NVFP4", + "capacity=4", + "lifecycle=list_models,load_model", + } { + if !strings.Contains(got, want) { + t.Errorf("nodes list output missing %q:\n%s", want, got) + } + } +} + func TestPatchYAMLPreservesArtifactBootstrapField(t *testing.T) { origYAML := `server: listen: "0.0.0.0:9090" diff --git a/apps/edge/internal/edgecmd/nodes.go b/apps/edge/internal/edgecmd/nodes.go index e02472d..fb54f28 100644 --- a/apps/edge/internal/edgecmd/nodes.go +++ b/apps/edge/internal/edgecmd/nodes.go @@ -48,8 +48,44 @@ When the server is offline or this command is run standalone, all nodes will sho fmt.Fprintf(cmd.OutOrStdout(), "%s\n", strings.Repeat("-", 100)) for _, n := range nodes { fmt.Fprintf(cmd.OutOrStdout(), "%-25s %-20s %-40s %-12s\n", n.ID, n.Alias, n.Token, "offline (configured)") + if summary := formatConfiguredProviders(n.Providers); summary != "" { + fmt.Fprintf(cmd.OutOrStdout(), " providers: %s\n", summary) + } } return nil }, } } + +func formatConfiguredProviders(providers []config.NodeProviderConf) string { + if len(providers) == 0 { + return "" + } + summaries := make([]string, 0, len(providers)) + for _, p := range providers { + if p.ID == "" { + continue + } + parts := []string{p.ID} + if p.Type != "" || p.Category != "" { + typeCategory := string(p.Category) + if p.Type != "" && typeCategory != "" { + typeCategory = p.Type + "/" + typeCategory + } else if p.Type != "" { + typeCategory = p.Type + } + parts = append(parts, "type="+typeCategory) + } + if len(p.Models) > 0 { + parts = append(parts, "models="+strings.Join(p.Models, ",")) + } + if p.Capacity > 0 { + parts = append(parts, fmt.Sprintf("capacity=%d", p.Capacity)) + } + if len(p.LifecycleCapabilities) > 0 { + parts = append(parts, "lifecycle="+strings.Join(p.LifecycleCapabilities, ",")) + } + summaries = append(summaries, strings.Join(parts, " ")) + } + return strings.Join(summaries, "; ") +}