From 8bb0c2babebec73da0e6b9c9b4a2a44a21f2a6bc Mon Sep 17 00:00:00 2001 From: toki Date: Fri, 12 Jun 2026 10:26:29 +0900 Subject: [PATCH] update: edge node store, transport, config changes and task archive --- .../USER_REVIEW.md | 63 +++++ .../code_review_cloud_G07_0.log | 144 +++++++++++ .../code_review_cloud_G07_1.log | 232 ++++++++++++++++++ .../code_review_cloud_G07_2.log | 229 +++++++++++++++++ .../complete.log | 53 ++++ .../plan_cloud_G07_0.log} | 0 .../plan_cloud_G07_1.log | 103 ++++++++ .../plan_cloud_G07_2.log | 92 +++++++ .../CODE_REVIEW-cloud-G07.md | 112 --------- apps/edge/internal/node/store.go | 6 +- apps/edge/internal/node/store_test.go | 66 +++++ apps/edge/internal/transport/server_test.go | 8 +- packages/go/config/config.go | 54 +++- packages/go/config/config_test.go | 86 +++++++ 14 files changed, 1121 insertions(+), 127 deletions(-) create mode 100644 agent-task/archive/2026/06/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/USER_REVIEW.md create mode 100644 agent-task/archive/2026/06/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/code_review_cloud_G07_0.log create mode 100644 agent-task/archive/2026/06/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/code_review_cloud_G07_1.log create mode 100644 agent-task/archive/2026/06/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/code_review_cloud_G07_2.log create mode 100644 agent-task/archive/2026/06/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/complete.log rename agent-task/{m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/PLAN-cloud-G07.md => archive/2026/06/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/plan_cloud_G07_0.log} (100%) create mode 100644 agent-task/archive/2026/06/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/plan_cloud_G07_1.log create mode 100644 agent-task/archive/2026/06/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/plan_cloud_G07_2.log delete mode 100644 agent-task/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/CODE_REVIEW-cloud-G07.md diff --git a/agent-task/archive/2026/06/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/USER_REVIEW.md b/agent-task/archive/2026/06/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/USER_REVIEW.md new file mode 100644 index 0000000..c644a6c --- /dev/null +++ b/agent-task/archive/2026/06/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/USER_REVIEW.md @@ -0,0 +1,63 @@ +# User Review Resolved - m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke + +## 요청 일시 + +2026-06-11 + +## 상태 + +RESOLVED + +## 사유 + +- 유형: environment-blocked -> user-resolved +- 현재 리뷰 회차: 3 +- 최종 판정: PASS/RESOLVED +- 요약: repo-owned normalize correctness follow-up은 통과했고, 사용자 소유 원격 환경에서 두 Ollama inference route와 동시 실행 smoke 증거가 수집되어 사용자가 완료 처리 진행을 승인했다. + +## 루프 이력 + +| Plan | Review | Verdict | 메모 | +|------|--------|---------|------| +| `plan_cloud_G07_0.log` | `code_review_cloud_G07_0.log` | FAIL | legacy adapter normalize가 raw config conflict를 숨기는 repo-owned correctness issue 발견 | +| `plan_cloud_G07_1.log` | `code_review_cloud_G07_1.log` | FAIL | same-name explicit disabled instance가 enabled legacy adapter를 가릴 수 있는 `Enabled` mismatch issue 발견 | +| `plan_cloud_G07_2.log` | `code_review_cloud_G07_2.log` | FAIL | repo-owned normalize fixes/tests/build는 통과했으나 field smoke 원격 환경 prerequisite이 남음 | +| `USER_REVIEW.md` | user decision | RESOLVED | 사용자 제공 원격 Node 연결 후 `gemma4_ext:26b`, `gemma4:26b` 개별/병렬 호출 성공 증거로 stop state 해소 | + +## 해소 근거 + +- 사용자 제공 원격 Node가 edge bootstrap으로 연결되었고, Edge route table은 두 모델을 같은 configured Node `node-ollama-1`로 라우팅하도록 설정되었다. +- `curl -fsS http://toki-labs.com:18081/healthz` 응답: `{"status":"ok"}` +- `curl -fsS http://toki-labs.com:18081/v1/models` 응답에 `gemma4_ext:26b`, `gemma4:26b`가 노출됨. +- `gemma4_ext:26b` 개별 호출 응답: `ext-ok` +- `gemma4:26b` 개별 호출 응답: `base-ok` +- 초기 병렬 호출에서 `concurrency limit exceeded (limit=1)`가 확인되어 `node-ollama-1.runtime.concurrency`를 `2`로 조정하고 Edge를 재시작함. +- 동시성 조정 후 병렬 호출 응답: + - `gemma4_ext:26b` -> `ext-parallel-ok` + - `gemma4:26b` -> `base-parallel-ok` +- 재검증 시 Edge 로그에 `concurrency limit exceeded`가 더 이상 나타나지 않음. +- 테스트 종료 후 사용자 요청에 따라 Edge 서버를 종료했고, `http://toki-labs.com:18081/healthz` 연결 실패로 포트가 내려간 상태를 확인함. +- 원래 Roadmap 문구의 CLI target 별도 호출은 이번 사용자 리뷰에서 사용자가 지정한 두 Ollama 추론엔진 smoke 범위로 축소 승인되었다. 완료 근거에는 실제 수집한 증거만 기록한다. +- 현재 archive plan: `plan_cloud_G07_2.log` +- 현재 archive review: `code_review_cloud_G07_2.log` +- 검증 명령: `go test -count=1 ./packages/go/config ./apps/edge/internal/node ./apps/edge/internal/transport ./apps/edge/internal/edgecmd`; `go test -count=1 ./...`; `go build ./apps/edge/... ./apps/node/...` +- 실제 출력: 대상 테스트 PASS, 전체 Go 테스트 PASS, edge/node build 성공. 상세 stdout은 `code_review_cloud_G07_2.log`의 `검증 결과` 섹션에 보관됨. +- 차단 해소 판단 근거: 원격 Edge 주소, 연결된 Node, 두 Ollama endpoint 준비와 접근 권한이 사용자 action으로 제공되었고, Edge 경유 OpenAI-compatible 호출이 두 model route에서 성공했다. + +## 사용자 결정 + +- [x] 사용자 소유 원격 환경을 준비하고 두 inference route smoke 증거를 수집했다. +- [x] 수집된 증거로 사용자 리뷰 stop state를 완료/PASS 해소하는 데 동의했다. +- [x] 테스트 종료 후 Edge 서버를 내렸다. + +## 재개 조건 + +해소 완료. 추가 재개 조건 없음. + +## 다음 실행 힌트 + +없음. + +## 종료 규칙 + +- `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다. diff --git a/agent-task/archive/2026/06/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/code_review_cloud_G07_0.log b/agent-task/archive/2026/06/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/code_review_cloud_G07_0.log new file mode 100644 index 0000000..ec1904a --- /dev/null +++ b/agent-task/archive/2026/06/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/code_review_cloud_G07_0.log @@ -0,0 +1,144 @@ + + +# Code Review Reference - TEST + +> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.** +> Record the exact remote/local environment, commands, stdout/stderr, and any artifact paths. +> If blocked by user-owned server, node, token, secret, or model endpoint preparation, fill `사용자 리뷰 요청` and stop. +> Do not ask the user directly. + +## 개요 + +date=2026-06-10 +task=m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke, plan=0, tag=TEST + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/node-multi-target-serving-foundation.md` +- Task ids: + - `field-smoke`: split-host smoke에서 하나의 연결된 Node가 최소 두 개의 inference model target과 하나의 CLI target을 같은 Edge-Node 연결로 호출할 수 있음을 확인한다. +- Completion mode: check-on-pass + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 종결 절차는 코드리뷰 에이전트 전용이다. + +smoke 증거가 같은 Edge-Node 연결과 같은 Node id를 가리키는지 확인한다. 환경 blocker가 repo-owned인지 user-owned인지 구분한다. + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [TEST-1] Split Host Multi Target Smoke | [사용자 리뷰 요청] | + +## 구현 체크리스트 + +- [x] split-host smoke 사전 조건과 실제 실행 config를 기록한다. (사전 회귀 테스트 통과, 환경 prerequisite 기록) +- [ ] Edge `/v1/models`가 두 inference model target을 노출하는 증거를 수집한다. — 원격 환경 필요 +- [ ] 같은 연결된 Node를 통해 inference model target 두 개를 각각 호출하는 증거를 수집한다. — 원격 환경 필요 +- [ ] 같은 연결된 Node를 통해 CLI target 하나를 호출하는 증거를 수집한다. — 원격 환경 필요 +- [x] 실패 시 repo-owned fix와 user-owned prerequisite을 구분해 기록한다. (아래 참조) +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. — 원격 smoke 완료 후 채울 수 있음 + +## 코드리뷰 전용 체크리스트 + +- [x] `코드리뷰 결과`에 판정을 append한다. +- [x] active `CODE_REVIEW-cloud-G07.md`를 `code_review_cloud_G07_N.log`로 아카이브한다. +- [x] active `PLAN-cloud-G07.md`를 `plan_cloud_G07_M.log`로 아카이브한다. +- [ ] PASS이면 `complete.log` 작성 후 archive 이동한다. +- [x] WARN/FAIL이면 후속 상태 또는 `USER_REVIEW.md`를 작성한다. + +## 계획 대비 변경 사항 + +사전 회귀 실행 중 `iop/apps/edge/internal/transport` 테스트 2개 실패 발견. smoke와 직접 관련된 repo-owned 버그였으므로 이 task 범위 내에서 최소 수정 처리. + +수정 내용: +1. `packages/go/config/config.go`: `NormalizeAdapters` exported 함수 추가 + `normalizeAdapters` idempotent 보장 (이미 promote된 이름은 재추가 안 함 + `hasInstanceName`/`hasVllmInstanceName` 헬퍼) +2. `apps/edge/internal/node/store.go`: `LoadFromConfig`에서 `config.NormalizeAdapters` 호출 추가 — `LoadEdge`를 거치지 않고 직접 `AdaptersConf.Ollama`를 사용한 `NodeDefinition`이 `BuildConfigPayload`에서 adapter를 찾지 못하는 버그 수정 +3. `apps/edge/internal/transport/server_test.go`: `TestBuildConfigPayload_OllamaVllmOneof` 테스트를 normalize된 `OllamaInstances`/`VllmInstances` 사용으로 수정 (테스트가 잘못된 normalize 전 상태를 사용했던 문제) + +## 주요 설계 결정 + +- `normalizeAdapters`를 idempotent하게 만든 것은 `LoadEdge` → `validateEdgeConfig` → `LoadFromConfig` 이중 호출 경로에서 중복 promote를 막기 위해서다. legacy `Ollama.Enabled`를 초기화하지 않고 이름 중복 검사로 처리해 기존 동작(`LoadEdge` 이후 `Ollama.Enabled` 접근)을 유지했다. + +## 사용자 리뷰 요청 + +- 상태: 차단 +- 사유 유형: 사용자 소유 외부 환경 준비 필요 +- 결정 필요: 없음 (설계 결정은 없음) +- 차단 근거: split-host smoke의 핵심 증거(Edge `/v1/models` multi-target 노출, inference 호출 두 개, CLI target 호출)는 원격 runner(`toki-labs.com`)와 연결된 Node, Node token, 모델 endpoint(Ollama)가 준비되어야 실행 가능하다. 현재 local 작업 컨테이너에서는 접근 불가. +- 실행한 검증/명령: + - `go test -count=1 ./...` → 전체 PASS (사전 회귀 포함) + - `go build ./apps/edge/... ./apps/node/...` → 빌드 성공 +- 자동 후속 불가 이유: 원격 Edge 주소(`toki-labs.com:18081`), 연결된 Node, Node token, Ollama endpoint는 사용자 소유 환경이다. 원격 runner SSH 접근도 현재 컨테이너에서 불가. +- 재개 조건: + 1. 원격 runner(`toki-labs.com`)에서 Edge와 Node가 실행 중이고, multi-target Edge config(inference 두 개 + CLI 한 개)가 적용되어 있어야 한다. + 2. `curl -fsS http://toki-labs.com:18081/v1/models` 응답에 두 inference route가 보여야 한다. + 3. 동일 Node id로 inference 호출 두 개와 CLI 호출 한 개가 성공해야 한다. + 4. 위 증거를 `CODE_REVIEW-cloud-G07.md` 검증 결과 섹션에 채운 뒤 코드리뷰를 재실행한다. + +## 리뷰어를 위한 체크포인트 + +- `/v1/models`에 두 inference route가 실제로 보인다. +- 두 inference 호출과 CLI 호출이 같은 connected Node id/alias를 사용한다. +- secret/token 값이 문서에 노출되지 않았다. +- 실패가 있으면 repo-owned follow-up과 user-owned blocker가 분리되어 있다. + +## 검증 결과 + +### TEST-1 중간 검증 + +```bash +$ go test -count=1 ./... +ok iop/apps/control-plane/cmd/control-plane 0.137s +ok iop/apps/control-plane/internal/wire 1.487s +ok iop/apps/edge/cmd/edge 0.066s +ok iop/apps/edge/internal/bootstrap 0.024s +ok iop/apps/edge/internal/controlplane 4.448s +ok iop/apps/edge/internal/edgecmd 0.019s +ok iop/apps/edge/internal/events 0.008s +ok iop/apps/edge/internal/input 0.006s +ok iop/apps/edge/internal/input/a2a 0.009s +ok iop/apps/edge/internal/node 0.005s +ok iop/apps/edge/internal/openai 1.509s +ok iop/apps/edge/internal/opsconsole 0.007s +ok iop/apps/edge/internal/service 0.006s +ok iop/apps/edge/internal/transport 2.018s +ok iop/apps/node/cmd/node 0.011s +ok iop/apps/node/internal/adapters 0.008s +ok iop/apps/node/internal/adapters/cli 42.117s +ok iop/apps/node/internal/adapters/cli/status 39.872s +ok iop/apps/node/internal/adapters/ollama 0.013s +ok iop/apps/node/internal/adapters/vllm 0.006s +ok iop/apps/node/internal/bootstrap 0.264s +ok iop/apps/node/internal/node 0.118s +ok iop/apps/node/internal/router 0.005s +ok iop/apps/node/internal/store 0.047s +ok iop/apps/node/internal/terminal 0.432s +ok iop/apps/node/internal/transport 5.141s +ok iop/packages/go/audit 0.002s +ok iop/packages/go/config 0.018s +ok iop/packages/go/hostsetup 0.006s +ok iop/packages/go/observability 0.009s +``` + +원격 smoke (healthz, /v1/models, inference 호출, CLI 호출)는 사용자 소유 환경 필요로 차단. + +### 최종 검증 + +원격 환경 준비 후 수집 예정: + +## 코드리뷰 결과 + +- 종합 판정: FAIL +- 차원별 평가: + - correctness: Fail + - completeness: Fail + - test coverage: Fail + - API contract: Fail + - code quality: Pass + - plan deviation: Warn + - verification trust: Pass +- 발견된 문제: + - Required: `packages/go/config/config.go:310`와 `packages/go/config/config.go:319`의 `hasInstanceName`/`hasVllmInstanceName` guard가 raw config의 첫 normalize와 이미 normalize된 config의 재호출을 구분하지 못합니다. 사용자가 legacy `ollama.enabled=true` 또는 `vllm.enabled=true`와 같은 이름의 explicit instance(`ollama`/`vllm`)를 함께 둔 경우, 새 코드는 legacy promotion을 건너뛰고 충돌을 조용히 통과시킵니다. 특히 explicit instance가 disabled이거나 endpoint/base_url이 다르면 `BuildConfigPayload`는 slice만 읽기 때문에 enabled legacy adapter가 Node payload에서 사라질 수 있습니다. idempotence는 유지하되 첫 normalize의 legacy/explicit same-name 충돌은 error로 거부하거나, 기존 instance가 promoted legacy 값과 정확히 같은 경우에만 재호출로 간주하도록 고치고, Ollama/Vllm 각각에 대해 `LoadEdge` 및 `node.LoadFromConfig` 회귀 테스트를 추가하세요. +- 다음 단계: WARN/FAIL follow-up으로 active plan/review를 다시 작성한다. user-owned 원격 smoke blocker는 유효하지만, repo-owned normalize correctness issue가 먼저 해결되어야 하므로 이번 루프에서는 `USER_REVIEW.md`를 만들지 않는다. diff --git a/agent-task/archive/2026/06/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/code_review_cloud_G07_1.log b/agent-task/archive/2026/06/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/code_review_cloud_G07_1.log new file mode 100644 index 0000000..11a23fb --- /dev/null +++ b/agent-task/archive/2026/06/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/code_review_cloud_G07_1.log @@ -0,0 +1,232 @@ + + +# Code Review Reference - REVIEW_TEST + +> **[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 user-only decision, user-owned external environment prerequisite, or scope conflict, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Evidence gaps that a follow-up agent can close by rerunning commands or collecting artifacts 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 the needed decision 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-11 +task=m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke, plan=1, tag=REVIEW_TEST + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/node-multi-target-serving-foundation.md` +- Task ids: + - `field-smoke`: split-host smoke에서 하나의 연결된 Node가 최소 두 개의 inference model target과 하나의 CLI target을 같은 Edge-Node 연결로 호출할 수 있음을 확인한다. +- Completion mode: check-on-pass + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. +리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다. + +1. 판정을 append한다. +2. `CODE_REVIEW-cloud-G07.md` → `code_review_cloud_G07_N.log`, `PLAN-cloud-G07.md` → `plan_cloud_G07_M.log`로 아카이브한다. +3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. +4. PASS이고 task group이 `m-`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다. +5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [REVIEW_TEST-1] NormalizeAdapters same-name conflict and smoke unblock | [x] | + +## 구현 체크리스트 + +- [x] `NormalizeAdapters`가 이미 normalize된 config를 재호출해도 중복 promote하지 않는 회귀 테스트를 Ollama/Vllm 각각 추가한다. (`TestNormalizeAdapters_OllamaIdempotent`, `TestNormalizeAdapters_VllmIdempotent`) +- [x] legacy enabled config와 같은 이름의 explicit instance가 raw config에 함께 있을 때 disabled 또는 다른 endpoint/base_url/context_size 충돌을 조용히 허용하지 않는 회귀 테스트를 Ollama/Vllm 각각 추가한다. (`TestNormalizeAdapters_OllamaConflictErrors`, `TestNormalizeAdapters_VllmConflictErrors`) +- [x] `NormalizeAdapters`를 수정해 exact promoted legacy instance 재호출은 idempotent하게 허용하고, 첫 normalize의 same-name conflicting explicit instance는 error로 반환하게 한다. (`normalizeAdapters`에 `findOllamaInstance`/`findVllmInstance` 기반 충돌 감지 추가) +- [x] `node.LoadFromConfig`가 raw legacy `NodeDefinition`을 받아도 normalized copy를 저장해 `BuildConfigPayload`에 Ollama/Vllm adapter가 포함되는지 검증한다. (`TestLoadFromConfig_LegacyOllamaInPayload`, `TestLoadFromConfig_LegacyVllmInPayload`) +- [x] `go test -count=1 ./packages/go/config ./apps/edge/internal/node ./apps/edge/internal/transport ./apps/edge/internal/edgecmd`, `go test -count=1 ./...`, `go build ./apps/edge/... ./apps/node/...`를 실행하고 실제 stdout/stderr를 기록한다. +- [x] 원격 field 환경이 준비되어 있으면 `/healthz`, `/v1/models`, inference target 두 개 호출, CLI target 호출이 같은 connected Node id/alias를 쓰는 증거를 수집한다. 준비되어 있지 않으면 `사용자 리뷰 요청`에 user-owned blocker와 재개 조건을 기록한다. — 원격 환경 차단, `사용자 리뷰 요청` 참조 +- [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_cloud_G07_N.log`로 아카이브한다. +- [x] active `PLAN-*-G??.md`를 `plan_cloud_G07_M.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-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/`를 `agent-task/archive/YYYY/MM/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. +- [ ] PASS이고 task group이 `m-`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다. +- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-node-multi-target-serving-foundation/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. +- [x] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-cloud-G07.md`와 `CODE_REVIEW-cloud-G07.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가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다. + +## 계획 대비 변경 사항 + +없음. 체크리스트 전 항목을 계획대로 구현했다. + +## 주요 설계 결정 + +- `normalizeAdapters`의 same-name 충돌 감지 기준: `Ollama.Enabled=true`이고 `OllamaInstances`에 이름 "ollama"가 이미 있을 때, `BaseURL`과 `ContextSize`가 모두 일치하면 idempotent(재promote 없이 통과), 하나라도 다르면 error. Vllm은 `Endpoint` 기준. 이렇게 하면 `LoadEdge` → `validateEdgeConfig` → `LoadFromConfig` 이중 normalize 경로에서 error 없이 통과하면서, raw config에서 진짜 충돌하는 경우는 error를 낸다. +- 헬퍼를 `hasInstanceName`/`hasVllmInstanceName`(단순 존재 확인)에서 `findOllamaInstance`/`findVllmInstance`(포인터 반환)로 교체해 충돌 값 비교까지 한 번에 처리했다. + +## 사용자 리뷰 요청 + +- 상태: 차단 +- 사유 유형: 사용자 소유 외부 환경 준비 필요 +- 결정 필요: 없음 +- 차단 근거: split-host smoke 증거(Edge `/v1/models` multi-target 노출, inference 두 개, CLI 호출)는 원격 runner(`toki-labs.com`)와 연결된 Node, Node token, Ollama endpoint가 필요하다. 현재 local 작업 컨테이너에서 접근 불가. +- 실행한 검증/명령: + - `go test -count=1 ./packages/go/config ./apps/edge/internal/node ./apps/edge/internal/transport ./apps/edge/internal/edgecmd` → 전체 PASS + - `go test -count=1 ./...` → 전체 PASS + - `go build ./apps/edge/... ./apps/node/...` → 성공 +- 자동 후속 불가 이유: 원격 Edge 주소(`toki-labs.com:18081`), 연결 Node, Node token, Ollama endpoint는 사용자 소유 환경이며 현재 컨테이너에서 SSH 접근 불가. +- 재개 조건: + 1. 원격 runner에서 Edge와 Node가 실행 중이고, multi-target Edge config(inference 두 개 + CLI 한 개)가 적용되어 있어야 한다. + 2. `curl -fsS http://toki-labs.com:18081/v1/models` 응답에 두 inference route가 보여야 한다. + 3. 동일 Node id로 inference 호출 두 개와 CLI 호출 한 개가 성공해야 한다. + 4. 위 증거를 `검증 결과` 섹션에 채운 뒤 코드리뷰를 재실행한다. + +## 리뷰어를 위한 체크포인트 + +- same-name explicit instance가 exact promoted legacy 값과 다르면 error가 난다. +- already normalized config를 다시 normalize해도 duplicate가 생기지 않는다. +- `node.LoadFromConfig` raw legacy path가 Ollama/Vllm adapter를 payload까지 유지한다. +- 원격 smoke가 실행된 경우 `/v1/models`, inference 두 개, CLI 호출이 같은 connected Node id/alias를 사용한다. +- 원격 smoke가 차단된 경우 blocker가 user-owned prerequisite인지와 재개 조건이 구체적인지 확인한다. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +필수 규칙: +- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다. +- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다. +- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다. +- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다. +- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다. + +### REVIEW_TEST-1 중간 검증 + +```bash +$ go test -count=1 ./packages/go/config ./apps/edge/internal/node ./apps/edge/internal/transport ./apps/edge/internal/edgecmd +ok iop/packages/go/config 0.018s +ok iop/apps/edge/internal/node 0.004s +ok iop/apps/edge/internal/transport 2.016s +ok iop/apps/edge/internal/edgecmd 0.007s +``` + +### 최종 검증 + +```bash +$ go test -count=1 ./... +ok iop/apps/control-plane/cmd/control-plane 0.135s +ok iop/apps/control-plane/internal/wire 1.477s +ok iop/apps/edge/cmd/edge 0.042s +ok iop/apps/edge/internal/bootstrap 0.019s +ok iop/apps/edge/internal/controlplane 4.456s +ok iop/apps/edge/internal/edgecmd 0.010s +ok iop/apps/edge/internal/events 0.004s +ok iop/apps/edge/internal/input 0.010s +ok iop/apps/edge/internal/input/a2a 0.006s +ok iop/apps/edge/internal/node 0.008s +ok iop/apps/edge/internal/openai 1.508s +ok iop/apps/edge/internal/opsconsole 0.011s +ok iop/apps/edge/internal/service 0.013s +ok iop/apps/edge/internal/transport 2.019s +ok iop/apps/node/cmd/node 0.011s +ok iop/apps/node/internal/adapters 0.009s +ok iop/apps/node/internal/adapters/cli 42.153s +ok iop/apps/node/internal/adapters/cli/status 39.808s +ok iop/apps/node/internal/adapters/ollama 0.008s +ok iop/apps/node/internal/adapters/vllm 0.014s +ok iop/apps/node/internal/bootstrap 0.264s +ok iop/apps/node/internal/node 0.120s +ok iop/apps/node/internal/router 0.005s +ok iop/apps/node/internal/store 0.045s +ok iop/apps/node/internal/terminal 0.443s +ok iop/apps/node/internal/transport 5.145s +ok iop/packages/go/audit 0.002s +ok iop/packages/go/config 0.013s +ok iop/packages/go/hostsetup 0.003s +ok iop/packages/go/observability 0.009s +``` + +```bash +$ go build ./apps/edge/... ./apps/node/... +(exit 0, no output) +``` + +원격 smoke는 사용자 소유 환경 차단으로 미실행. `사용자 리뷰 요청` 참조. + +```bash +$ curl -fsS http://toki-labs.com:18081/healthz +(원격 환경 준비 후 수집 예정) +``` + +```bash +$ curl -fsS http://toki-labs.com:18081/v1/models +(원격 환경 준비 후 수집 예정) +``` + +```bash +$ curl -fsS http://toki-labs.com:18081/v1/chat/completions -H 'Content-Type: application/json' -d '{"model":"","messages":[{"role":"user","content":"ping"}]}' +(원격 환경 준비 후 수집 예정) +``` + +```bash +$ curl -fsS http://toki-labs.com:18081/v1/chat/completions -H 'Content-Type: application/json' -d '{"model":"","messages":[{"role":"user","content":"ping"}]}' +(원격 환경 준비 후 수집 예정) +``` + +```bash +$ +(원격 환경 준비 후 수집 예정) +``` + +--- + +> **[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. + +Sections and their ownership: + +| 섹션 | 소유자 | 설명 | +|------|--------|------| +| 헤더 주석, 개요(date/task/plan/tag), 리뷰 에이전트 지시 | 스텁 생성 시 고정 | 구현 에이전트가 수정하거나 실행하지 않음 | +| Roadmap Targets | follow-up plan에서 복사해 스텁 생성 시 고정 | 구현 에이전트가 수정하지 않음; PASS 시 code-review가 `complete.log`의 `Roadmap Completion`으로 복사 | +| 구현 항목별 완료 여부 (항목명) | 스텁 생성 시 고정 | `[ ]` → `[x]` 체크만 구현 에이전트가 수행 | +| 구현 체크리스트 (항목 텍스트/순서) | follow-up plan에서 복사해 스텁 생성 시 고정 | 구현 에이전트가 `[ ]` → `[x]` 체크만 수행; 마지막 체크박스는 저장 전 필수 | +| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify or check this section | +| 계획 대비 변경 사항, 주요 설계 결정 | 구현 에이전트가 채움 | placeholder 텍스트를 실제 내용으로 교체 | +| 사용자 리뷰 요청 | 구현 에이전트가 채움 | 진행에 사용자 입력이 필요하지 않으면 `상태: 없음` 유지; 구현 중 직접 질문은 금지; 필요하면 결정 항목, 근거, 명령 출력, 자동 후속 불가 이유, 재개 조건을 기록 | +| 리뷰어를 위한 체크포인트 | 스텁 생성 시 고정 | 계획에서 추출한 리뷰 포인트 | +| 검증 결과 (섹션 제목 + 명령) | 스텁 생성 시 고정 | 실행 출력만 구현 에이전트가 채움; 명령 변경은 `계획 대비 변경 사항`에 기록 | +| 코드리뷰 결과 | 리뷰 에이전트가 append | 스텁에 포함하지 않음 | + +## 코드리뷰 결과 + +- 종합 판정: FAIL +- 차원별 평가: + - correctness: Fail + - completeness: Fail + - test coverage: Fail + - API contract: Fail + - code quality: Pass + - plan deviation: Fail + - verification trust: Pass +- 발견된 문제: + - Required: `packages/go/config/config.go:319`와 `packages/go/config/config.go:331`에서 existing same-name instance의 `Enabled` 값을 비교하지 않습니다. 후속 계획은 legacy enabled config와 같은 이름의 explicit instance가 raw config에 함께 있을 때 “disabled 또는 다른 endpoint/base_url/context_size 충돌”을 거부하라고 했지만, 현재 구현은 `BaseURL`/`ContextSize` 또는 `Endpoint`만 비교합니다. 따라서 `ollama.enabled=true`와 `ollama_instances: [{name: "ollama", enabled: false, base_url: same, context_size: same}]` 조합은 error 없이 통과하고, 이후 `BuildConfigPayload`는 disabled explicit instance를 skip해 enabled legacy adapter를 payload에서 누락합니다. Vllm도 같은 문제가 있습니다. `Enabled`가 legacy promoted 값과 일치하는지도 비교하고, disabled same-name explicit instance 회귀 테스트를 Ollama/Vllm 양쪽에 추가하세요. +- 다음 단계: WARN/FAIL follow-up으로 active plan/review를 다시 작성한다. 원격 field smoke blocker는 user-owned로 보이나, repo-owned normalize correctness issue가 아직 남아 있어 이번 루프에서는 `USER_REVIEW.md`를 만들지 않는다. diff --git a/agent-task/archive/2026/06/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/code_review_cloud_G07_2.log b/agent-task/archive/2026/06/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/code_review_cloud_G07_2.log new file mode 100644 index 0000000..739ed92 --- /dev/null +++ b/agent-task/archive/2026/06/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/code_review_cloud_G07_2.log @@ -0,0 +1,229 @@ + + +# Code Review Reference - REVIEW_REVIEW_TEST + +> **[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 user-only decision, user-owned external environment prerequisite, or scope conflict, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Evidence gaps that a follow-up agent can close by rerunning commands or collecting artifacts 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 the needed decision 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-11 +task=m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke, plan=2, tag=REVIEW_REVIEW_TEST + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/node-multi-target-serving-foundation.md` +- Task ids: + - `field-smoke`: split-host smoke에서 하나의 연결된 Node가 최소 두 개의 inference model target과 하나의 CLI target을 같은 Edge-Node 연결로 호출할 수 있음을 확인한다. +- Completion mode: check-on-pass + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. +리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다. + +1. 판정을 append한다. +2. `CODE_REVIEW-cloud-G07.md` → `code_review_cloud_G07_N.log`, `PLAN-cloud-G07.md` → `plan_cloud_G07_M.log`로 아카이브한다. +3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. +4. PASS이고 task group이 `m-`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다. +5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [REVIEW_REVIEW_TEST-1] Enabled mismatch conflict | [x] | + +## 구현 체크리스트 + +- [x] `NormalizeAdapters`가 legacy enabled config와 same-name explicit disabled instance를 Ollama/Vllm 각각 error로 거부하게 한다. (`!existing.Enabled` 조건 추가) +- [x] disabled same-name explicit instance 회귀 테스트를 Ollama/Vllm 각각 추가한다. (`TestNormalizeAdapters_OllamaDisabledSameNameErrors`, `TestNormalizeAdapters_VllmDisabledSameNameErrors`) +- [x] idempotent 재호출 테스트가 여전히 통과하도록 exact promoted legacy instance는 enabled 값까지 동일할 때만 허용한다. (promoted instance는 `Enabled=true`이므로 재호출 시 일치 → 통과) +- [x] `go test -count=1 ./packages/go/config ./apps/edge/internal/node ./apps/edge/internal/transport ./apps/edge/internal/edgecmd`, `go test -count=1 ./...`, `go build ./apps/edge/... ./apps/node/...`를 실행하고 실제 stdout/stderr를 기록한다. +- [x] 원격 field 환경이 준비되어 있지 않아 `사용자 리뷰 요청`에 기록한다. +- [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_cloud_G07_N.log`로 아카이브한다. +- [x] active `PLAN-*-G??.md`를 `plan_cloud_G07_M.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-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/`를 `agent-task/archive/YYYY/MM/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. +- [ ] PASS이고 task group이 `m-`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다. +- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-node-multi-target-serving-foundation/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. +- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-cloud-G07.md`와 `CODE_REVIEW-cloud-G07.md`를 작성하고 `complete.log`를 작성하지 않는다. +- [x] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다. +- [x] USER_REVIEW가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다. + +## 계획 대비 변경 사항 + +없음. 체크리스트 전 항목을 계획대로 구현했다. + +## 주요 설계 결정 + +- `!existing.Enabled` 조건을 충돌 감지에 추가해 legacy `Enabled=true` + explicit `Enabled=false` same-name 조합을 error로 처리. idempotent 경로(promoted legacy → `Enabled=true`)와 구분된다. + +## 사용자 리뷰 요청 + +- 상태: 차단 +- 사유 유형: 사용자 소유 외부 환경 준비 필요 +- 결정 필요: 없음 +- 차단 근거: split-host smoke 증거(Edge `/v1/models` multi-target 노출, inference 두 개, CLI 호출)는 원격 runner(`toki-labs.com`)와 연결된 Node, Node token, Ollama endpoint가 필요하다. 현재 local 작업 컨테이너에서 접근 불가. +- 실행한 검증/명령: + - `go test -count=1 ./packages/go/config ./apps/edge/internal/node ./apps/edge/internal/transport ./apps/edge/internal/edgecmd` → 전체 PASS + - `go test -count=1 ./...` → 전체 PASS + - `go build ./apps/edge/... ./apps/node/...` → 성공 +- 자동 후속 불가 이유: 원격 Edge 주소(`toki-labs.com:18081`), 연결 Node, Node token, Ollama endpoint는 사용자 소유 환경이며 현재 컨테이너에서 SSH 접근 불가. +- 재개 조건: + 1. 원격 runner에서 Edge와 Node가 실행 중이고, multi-target Edge config(inference 두 개 + CLI 한 개)가 적용되어 있어야 한다. + 2. `curl -fsS http://toki-labs.com:18081/v1/models` 응답에 두 inference route가 보여야 한다. + 3. 동일 Node id로 inference 호출 두 개와 CLI 호출 한 개가 성공해야 한다. + 4. 위 증거를 `검증 결과` 섹션에 채운 뒤 코드리뷰를 재실행한다. + +## 리뷰어를 위한 체크포인트 + +- legacy enabled config와 same-name explicit disabled instance가 Ollama/Vllm 모두 error로 거부된다. +- idempotent 재호출은 enabled 값까지 동일한 promoted legacy instance에서만 통과한다. +- 원격 smoke가 실행된 경우 `/v1/models`, inference 두 개, CLI 호출이 같은 connected Node id/alias를 사용한다. +- 원격 smoke가 차단된 경우 blocker가 user-owned prerequisite인지와 재개 조건이 구체적인지 확인한다. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +필수 규칙: +- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다. +- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다. +- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다. +- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다. +- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다. + +### REVIEW_REVIEW_TEST-1 중간 검증 + +```bash +$ go test -count=1 ./packages/go/config ./apps/edge/internal/node ./apps/edge/internal/transport ./apps/edge/internal/edgecmd +ok iop/packages/go/config 0.016s +ok iop/apps/edge/internal/node 0.004s +ok iop/apps/edge/internal/transport 2.010s +ok iop/apps/edge/internal/edgecmd 0.007s +``` + +### 최종 검증 + +```bash +$ go test -count=1 ./... +ok iop/apps/control-plane/cmd/control-plane 0.149s +ok iop/apps/control-plane/internal/wire 1.479s +ok iop/apps/edge/cmd/edge 0.035s +ok iop/apps/edge/internal/bootstrap 0.017s +ok iop/apps/edge/internal/controlplane 4.447s +ok iop/apps/edge/internal/edgecmd 0.008s +ok iop/apps/edge/internal/events 0.003s +ok iop/apps/edge/internal/input 0.005s +ok iop/apps/edge/internal/input/a2a 0.005s +ok iop/apps/edge/internal/node 0.004s +ok iop/apps/edge/internal/openai 1.508s +ok iop/apps/edge/internal/opsconsole 0.007s +ok iop/apps/edge/internal/service 0.006s +ok iop/apps/edge/internal/transport 2.017s +ok iop/apps/node/cmd/node 0.010s +ok iop/apps/node/internal/adapters 0.008s +ok iop/apps/node/internal/adapters/cli 42.105s +ok iop/apps/node/internal/adapters/cli/status 39.816s +ok iop/apps/node/internal/adapters/ollama 0.016s +ok iop/apps/node/internal/adapters/vllm 0.015s +ok iop/apps/node/internal/bootstrap 0.263s +ok iop/apps/node/internal/node 0.120s +ok iop/apps/node/internal/router 0.004s +ok iop/apps/node/internal/store 0.062s +ok iop/apps/node/internal/terminal 0.438s +ok iop/apps/node/internal/transport 5.139s +ok iop/packages/go/audit 0.002s +ok iop/packages/go/config 0.011s +ok iop/packages/go/hostsetup 0.004s +ok iop/packages/go/observability 0.009s +``` + +```bash +$ go build ./apps/edge/... ./apps/node/... +(exit 0, no output) +``` + +원격 smoke는 사용자 소유 환경 차단으로 미실행. `사용자 리뷰 요청` 참조. + +```bash +$ curl -fsS http://toki-labs.com:18081/healthz +(원격 환경 준비 후 수집 예정) +``` + +```bash +$ curl -fsS http://toki-labs.com:18081/v1/models +(원격 환경 준비 후 수집 예정) +``` + +```bash +$ curl -fsS http://toki-labs.com:18081/v1/chat/completions -H 'Content-Type: application/json' -d '{"model":"","messages":[{"role":"user","content":"ping"}]}' +(원격 환경 준비 후 수집 예정) +``` + +```bash +$ curl -fsS http://toki-labs.com:18081/v1/chat/completions -H 'Content-Type: application/json' -d '{"model":"","messages":[{"role":"user","content":"ping"}]}' +(원격 환경 준비 후 수집 예정) +``` + +```bash +$ +(원격 환경 준비 후 수집 예정) +``` + +--- + +> **[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. + +Sections and their ownership: + +| 섹션 | 소유자 | 설명 | +|------|--------|------| +| 헤더 주석, 개요(date/task/plan/tag), 리뷰 에이전트 지시 | 스텁 생성 시 고정 | 구현 에이전트가 수정하거나 실행하지 않음 | +| Roadmap Targets | follow-up plan에서 복사해 스텁 생성 시 고정 | 구현 에이전트가 수정하지 않음; PASS 시 code-review가 `complete.log`의 `Roadmap Completion`으로 복사 | +| 구현 항목별 완료 여부 (항목명) | 스텁 생성 시 고정 | `[ ]` → `[x]` 체크만 구현 에이전트가 수행 | +| 구현 체크리스트 (항목 텍스트/순서) | follow-up plan에서 복사해 스텁 생성 시 고정 | 구현 에이전트가 `[ ]` → `[x]` 체크만 수행; 마지막 체크박스는 저장 전 필수 | +| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify or check this section | +| 계획 대비 변경 사항, 주요 설계 결정 | 구현 에이전트가 채움 | placeholder 텍스트를 실제 내용으로 교체 | +| 사용자 리뷰 요청 | 구현 에이전트가 채움 | 진행에 사용자 입력이 필요하지 않으면 `상태: 없음` 유지; 구현 중 직접 질문은 금지; 필요하면 결정 항목, 근거, 명령 출력, 자동 후속 불가 이유, 재개 조건을 기록 | +| 리뷰어를 위한 체크포인트 | 스텁 생성 시 고정 | 계획에서 추출한 리뷰 포인트 | +| 검증 결과 (섹션 제목 + 명령) | 스텁 생성 시 고정 | 실행 출력만 구현 에이전트가 채움; 명령 변경은 `계획 대비 변경 사항`에 기록 | +| 코드리뷰 결과 | 리뷰 에이전트가 append | 스텁에 포함하지 않음 | + +## 코드리뷰 결과 + +- 종합 판정: FAIL +- 차원별 평가: + - correctness: Pass + - completeness: Fail + - test coverage: Fail + - API contract: Pass + - code quality: Pass + - plan deviation: Pass + - verification trust: Pass +- 발견된 문제: + - Required: `agent-task/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/CODE_REVIEW-cloud-G07.md:91`의 `사용자 리뷰 요청`대로 split-host field smoke 핵심 증거가 아직 없다. `/healthz`, `/v1/models`, 두 inference target 호출, CLI target 호출이 같은 connected Node id/alias를 통해 성공했다는 증거가 없으므로 Roadmap target `field-smoke`는 완료로 판정할 수 없다. 다만 남은 blocker는 repo-owned 코드 결함이 아니라 원격 runner, 연결된 Node, Node token, Ollama endpoint 준비라는 user-owned 외부 환경 prerequisite이다. +- 다음 단계: USER_REVIEW stop state를 작성한다. 자동 follow-up plan으로 해결 가능한 repo-owned 이슈는 현재 남아 있지 않으며, 원격 field 환경 준비 후 동일 task를 재개해야 한다. diff --git a/agent-task/archive/2026/06/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/complete.log b/agent-task/archive/2026/06/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/complete.log new file mode 100644 index 0000000..c1b2675 --- /dev/null +++ b/agent-task/archive/2026/06/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/complete.log @@ -0,0 +1,53 @@ +# Complete - m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke + +## 완료 일시 + +2026-06-12 + +## 요약 + +3회 plan/review loop 이후 USER_REVIEW stop state를 원격 Edge/Node field smoke 증거와 사용자 승인으로 해소했다. 최종 판정은 PASS/RESOLVED. + +## 루프 이력 + +| Plan | Review | Verdict | 메모 | +|------|--------|---------|------| +| `plan_cloud_G07_0.log` | `code_review_cloud_G07_0.log` | FAIL | legacy adapter normalize가 raw config conflict를 숨기는 repo-owned correctness issue 발견 | +| `plan_cloud_G07_1.log` | `code_review_cloud_G07_1.log` | FAIL | same-name explicit disabled instance가 enabled legacy adapter를 가릴 수 있는 `Enabled` mismatch issue 발견 | +| `plan_cloud_G07_2.log` | `code_review_cloud_G07_2.log` | FAIL | repo-owned normalize fixes/tests/build는 통과했으나 field smoke 원격 환경 prerequisite이 남아 USER_REVIEW로 전환 | +| `USER_REVIEW.md` | user decision | PASS/RESOLVED | 사용자 제공 원격 Node 연결 후 두 Ollama inference route 개별/병렬 호출 성공 증거로 stop state 해소 | + +## 구현/정리 내용 + +- `NormalizeAdapters`의 legacy enabled config와 same-name explicit disabled instance 충돌을 Ollama/Vllm 양쪽에서 error로 거부하도록 보정했다. +- disabled same-name explicit instance 회귀 테스트와 legacy raw config payload 유지 테스트를 추가했다. +- 사용자 제공 원격 환경에 Mac용 node binary/bootstrap을 준비하고, Edge config에 두 Ollama route를 적용해 field smoke를 수행했다. +- 원격 smoke 완료 후 사용자 요청에 따라 Edge 서버를 종료했다. + +## 최종 검증 + +- `go test -count=1 ./packages/go/config ./apps/edge/internal/node ./apps/edge/internal/transport ./apps/edge/internal/edgecmd` - PASS; 상세 stdout은 `code_review_cloud_G07_2.log`의 `검증 결과` 섹션에 보관됨. +- `go test -count=1 ./...` - PASS; 상세 stdout은 `code_review_cloud_G07_2.log`의 `검증 결과` 섹션에 보관됨. +- `go build ./apps/edge/... ./apps/node/...` - PASS; exit 0, output 없음. +- `curl -fsS http://toki-labs.com:18081/healthz` - PASS; `{"status":"ok"}`. +- `curl -fsS http://toki-labs.com:18081/v1/models` - PASS; `gemma4_ext:26b`, `gemma4:26b` 노출 확인. +- `curl -sS --max-time 180 http://toki-labs.com:18081/v1/chat/completions ... model=gemma4_ext:26b` - PASS; 응답 content `ext-ok`. +- `curl -sS --max-time 180 http://toki-labs.com:18081/v1/chat/completions ... model=gemma4:26b` - PASS; 응답 content `base-ok`. +- `node-ollama-1.runtime.concurrency=2` 적용 후 두 `curl ... /v1/chat/completions` 병렬 호출 - PASS; `gemma4_ext:26b` 응답 `ext-parallel-ok`, `gemma4:26b` 응답 `base-parallel-ok`. +- `tail -n 80 edge.log | grep -i "concurrency\\|limit exceeded"` - PASS; 동시성 조정 후 관련 오류 없음. +- `curl -sS --max-time 5 http://toki-labs.com:18081/healthz` after shutdown - PASS; 사용자 요청에 따라 Edge 서버 종료 후 연결 실패 확인. + +## Roadmap Completion + +- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/node-multi-target-serving-foundation.md` +- Completed task ids: + - `field-smoke`: PASS/RESOLVED; evidence=`plan_cloud_G07_2.log`, `code_review_cloud_G07_2.log`, `USER_REVIEW.md`; verification=`go test -count=1 ./packages/go/config ./apps/edge/internal/node ./apps/edge/internal/transport ./apps/edge/internal/edgecmd`, `go test -count=1 ./...`, `go build ./apps/edge/... ./apps/node/...`, 원격 Edge `/healthz`, `/v1/models`, 두 Ollama inference route 개별/병렬 호출 +- Not completed task ids: 없음 + +## 잔여 Nit + +- 없음 + +## 후속 작업 + +- 없음 diff --git a/agent-task/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/PLAN-cloud-G07.md b/agent-task/archive/2026/06/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/plan_cloud_G07_0.log similarity index 100% rename from agent-task/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/PLAN-cloud-G07.md rename to agent-task/archive/2026/06/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/plan_cloud_G07_0.log diff --git a/agent-task/archive/2026/06/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/plan_cloud_G07_1.log b/agent-task/archive/2026/06/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/plan_cloud_G07_1.log new file mode 100644 index 0000000..57a83f1 --- /dev/null +++ b/agent-task/archive/2026/06/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/plan_cloud_G07_1.log @@ -0,0 +1,103 @@ + + +# Plan - REVIEW_TEST + +## 이 파일을 읽는 구현 에이전트에게 + +이 작업은 직전 code review `FAIL`의 Required 이슈만 먼저 고친 뒤 field smoke 종결 상태를 다시 판단하는 후속 작업이다. 사용자에게 직접 질문하지 말고, 원격 서버/노드/token/모델 endpoint 준비가 없어 smoke를 끝까지 실행할 수 없으면 `CODE_REVIEW-cloud-G07.md`의 `사용자 리뷰 요청`에 정확한 증거와 재개 조건을 남긴다. + +## 배경 + +직전 구현은 `node.LoadFromConfig`에서도 legacy adapter config를 normalize하도록 보정했지만, idempotence를 위해 추가한 same-name guard가 첫 normalize와 재호출을 구분하지 못한다. raw config에 legacy `ollama`/`vllm`과 같은 이름의 explicit instance가 함께 있으면 legacy adapter가 조용히 무시될 수 있어, field smoke 전에 repo-owned correctness issue를 먼저 닫아야 한다. + +## 사용자 리뷰 요청 흐름 + +원격 runner, 연결된 Node, Node token, Ollama endpoint, CLI target 같은 사용자 소유 prerequisite이 없으면 진행할 수 없는 경우 active review stub의 `사용자 리뷰 요청`에 기록한다. code-review가 user-review stop 여부를 결정한다. 단, normalize correctness issue와 테스트 증거 공백은 repo-owned 후속 작업으로 해결한다. + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/node-multi-target-serving-foundation.md` +- Task ids: + - `field-smoke`: split-host smoke에서 하나의 연결된 Node가 최소 두 개의 inference model target과 하나의 CLI target을 같은 Edge-Node 연결로 호출할 수 있음을 확인한다. +- Completion mode: check-on-pass + +## 분석 결과 + +### 읽은 파일 + +- `agent-task/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/plan_cloud_G07_0.log` +- `agent-task/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/code_review_cloud_G07_0.log` +- `packages/go/config/config.go` +- `packages/go/config/config_test.go` +- `apps/edge/internal/node/store.go` +- `apps/edge/internal/node/store_test.go` +- `apps/edge/internal/node/mapper.go` +- `apps/edge/internal/transport/server_test.go` +- `apps/edge/internal/edgecmd/config.go` +- `agent-test/local/rules.md` +- `agent-test/local/edge-smoke.md` +- `agent-test/local/platform-common-smoke.md` +- `agent-test/local/testing-smoke.md` + +### 리뷰 실패 요약 + +- Required: `NormalizeAdapters`가 `hasInstanceName(..., "ollama")` / `hasVllmInstanceName(..., "vllm")`만으로 legacy promotion을 건너뛰어 raw config의 conflicting same-name instance를 조용히 허용한다. +- 영향: `BuildConfigPayload`는 normalized slice만 읽으므로, explicit same-name instance가 disabled이거나 legacy field와 값이 다르면 enabled legacy adapter가 Node payload에서 빠질 수 있다. +- `go test -count=1 ./...`와 `go build ./apps/edge/... ./apps/node/...`는 통과했지만, 해당 edge case를 직접 검증하는 테스트가 없다. + +### 범위 결정 근거 + +- 이번 후속 범위는 normalize idempotence/conflict handling과 그 회귀 테스트로 제한한다. +- field smoke 자체는 이 fix 후에도 원격 환경이 준비되어야 완료 가능하다. +- docs, roadmap, archive 문서는 수정하지 않는다. + +### 빌드 등급 + +- build: `cloud-G07`, review: `cloud-G07`. Required fix는 deterministic code/test work지만, 이 subtask의 종료 판단은 원격 field smoke와 user-review gate가 함께 걸려 있다. + +## 구현 체크리스트 + +- [ ] `NormalizeAdapters`가 이미 normalize된 config를 재호출해도 중복 promote하지 않는 회귀 테스트를 Ollama/Vllm 각각 추가한다. +- [ ] legacy enabled config와 같은 이름의 explicit instance가 raw config에 함께 있을 때 disabled 또는 다른 endpoint/base_url/context_size 충돌을 조용히 허용하지 않는 회귀 테스트를 Ollama/Vllm 각각 추가한다. +- [ ] `NormalizeAdapters`를 수정해 exact promoted legacy instance 재호출은 idempotent하게 허용하고, 첫 normalize의 same-name conflicting explicit instance는 error로 반환하게 한다. +- [ ] `node.LoadFromConfig`가 raw legacy `NodeDefinition`을 받아도 normalized copy를 저장해 `BuildConfigPayload`에 Ollama/Vllm adapter가 포함되는지 검증한다. +- [ ] `go test -count=1 ./packages/go/config ./apps/edge/internal/node ./apps/edge/internal/transport ./apps/edge/internal/edgecmd`, `go test -count=1 ./...`, `go build ./apps/edge/... ./apps/node/...`를 실행하고 실제 stdout/stderr를 기록한다. +- [ ] 원격 field 환경이 준비되어 있으면 `/healthz`, `/v1/models`, inference target 두 개 호출, CLI target 호출이 같은 connected Node id/alias를 쓰는 증거를 수집한다. 준비되어 있지 않으면 `사용자 리뷰 요청`에 user-owned blocker와 재개 조건을 기록한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +### [REVIEW_TEST-1] NormalizeAdapters same-name conflict and smoke unblock + +- 문제: idempotence guard가 raw config conflict를 숨겨 enabled legacy adapter가 payload에서 사라질 수 있다. +- 해결 방법: promoted legacy instance와 기존 same-name instance를 비교하는 helper를 두고, 값이 정확히 같으면 재호출로 간주해 skip한다. 값이 다르거나 disabled 상태가 충돌하면 error를 반환한다. Vllm도 같은 규칙을 적용한다. +- 수정 파일 및 체크리스트: + - [ ] `packages/go/config/config.go`: legacy promotion conflict 판정과 idempotent 재호출 허용을 구현한다. + - [ ] `packages/go/config/config_test.go`: LoadEdge 기준 idempotence와 same-name conflict 회귀 테스트를 추가한다. + - [ ] `apps/edge/internal/node/store_test.go`: raw `NodeDefinition`을 `LoadFromConfig`에 넣는 경로에서 legacy adapter가 payload까지 유지되는지 검증한다. + - [ ] 필요 시 `apps/edge/internal/transport/server_test.go`의 기존 oneof 테스트를 새 normalize 계약과 맞춘다. +- 테스트 작성: 필수. 이번 Required 이슈는 기존 테스트가 놓친 edge case이므로 테스트 없이 수정하지 않는다. +- 중간 검증: + - `go test -count=1 ./packages/go/config ./apps/edge/internal/node ./apps/edge/internal/transport ./apps/edge/internal/edgecmd` + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `packages/go/config/config.go` | REVIEW_TEST-1 | +| `packages/go/config/config_test.go` | REVIEW_TEST-1 | +| `apps/edge/internal/node/store_test.go` | REVIEW_TEST-1 | +| `apps/edge/internal/transport/server_test.go` | REVIEW_TEST-1 | +| `agent-task/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/CODE_REVIEW-cloud-G07.md` | REVIEW_TEST-1 | + +## 최종 검증 + +```bash +go test -count=1 ./packages/go/config ./apps/edge/internal/node ./apps/edge/internal/transport ./apps/edge/internal/edgecmd +go test -count=1 ./... +go build ./apps/edge/... ./apps/node/... +curl -fsS http:///healthz +curl -fsS http:///v1/models +curl -fsS http:///v1/chat/completions -H 'Content-Type: application/json' -d '{"model":"","messages":[{"role":"user","content":"ping"}]}' +curl -fsS http:///v1/chat/completions -H 'Content-Type: application/json' -d '{"model":"","messages":[{"role":"user","content":"ping"}]}' +``` + +CLI target 호출은 프로젝트의 최신 Edge command endpoint 또는 `iop-edge` command로 실행하고, 실제 명령과 출력을 `CODE_REVIEW-cloud-G07.md`에 기록한다. 원격 환경이 사용자 소유 prerequisite 때문에 불가하면 실행하지 못한 field smoke 명령과 정확한 blocker를 `사용자 리뷰 요청`에 기록한다. diff --git a/agent-task/archive/2026/06/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/plan_cloud_G07_2.log b/agent-task/archive/2026/06/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/plan_cloud_G07_2.log new file mode 100644 index 0000000..849cf85 --- /dev/null +++ b/agent-task/archive/2026/06/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/plan_cloud_G07_2.log @@ -0,0 +1,92 @@ + + +# Plan - REVIEW_REVIEW_TEST + +## 이 파일을 읽는 구현 에이전트에게 + +이 작업은 직전 code review `FAIL`의 남은 Required 이슈만 고친다. 사용자에게 직접 질문하지 말고, 원격 field smoke가 여전히 사용자 소유 환경 때문에 불가능하면 `CODE_REVIEW-cloud-G07.md`의 `사용자 리뷰 요청`에 증거와 재개 조건을 남긴다. + +## 배경 + +직전 후속 구현은 `NormalizeAdapters`의 same-name conflict에서 base URL, context size, endpoint mismatch는 잡았지만 `Enabled` mismatch를 비교하지 않았다. legacy field가 enabled인데 explicit same-name instance가 disabled이면 normalize는 통과하고, `BuildConfigPayload`는 disabled instance를 skip해 enabled legacy adapter가 payload에서 사라질 수 있다. + +## 사용자 리뷰 요청 흐름 + +원격 runner, 연결된 Node, Node token, Ollama endpoint, CLI target 같은 사용자 소유 prerequisite이 없으면 진행할 수 없는 경우 active review stub의 `사용자 리뷰 요청`에 기록한다. code-review가 user-review stop 여부를 결정한다. repo-owned normalize correctness issue와 테스트 증거 공백은 사용자 리뷰 요청으로 넘기지 않는다. + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/node-multi-target-serving-foundation.md` +- Task ids: + - `field-smoke`: split-host smoke에서 하나의 연결된 Node가 최소 두 개의 inference model target과 하나의 CLI target을 같은 Edge-Node 연결로 호출할 수 있음을 확인한다. +- Completion mode: check-on-pass + +## 분석 결과 + +### 읽은 파일 + +- `agent-task/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/plan_cloud_G07_1.log` +- `agent-task/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/code_review_cloud_G07_1.log` +- `packages/go/config/config.go` +- `packages/go/config/config_test.go` +- `apps/edge/internal/node/store_test.go` +- `apps/edge/internal/node/mapper.go` + +### 리뷰 실패 요약 + +- Required: `NormalizeAdapters`가 same-name explicit instance의 `Enabled` mismatch를 conflict로 처리하지 않는다. +- 영향: raw config에 `ollama.enabled=true`와 `ollama_instances[].name="ollama", enabled=false`가 같은 URL/context로 함께 있으면 adapter가 payload에서 누락될 수 있다. Vllm도 동일하다. +- 기존 테스트는 다른 URL/endpoint만 검증하고 disabled same-name instance를 직접 검증하지 않는다. + +### 범위 결정 근거 + +- 이번 후속 범위는 `Enabled` conflict handling과 disabled explicit instance 회귀 테스트로 제한한다. +- 이미 추가된 legacy payload 유지 테스트와 idempotence 테스트는 유지한다. +- field smoke 자체는 이 fix 후에도 원격 환경이 준비되어야 완료 가능하다. + +### 빌드 등급 + +- build: `cloud-G07`, review: `cloud-G07`. repo-owned fix는 좁지만 이 subtask의 종료 판단은 원격 field smoke와 user-review gate가 함께 걸려 있다. + +## 구현 체크리스트 + +- [ ] `NormalizeAdapters`가 legacy enabled config와 same-name explicit disabled instance를 Ollama/Vllm 각각 error로 거부하게 한다. +- [ ] disabled same-name explicit instance 회귀 테스트를 Ollama/Vllm 각각 추가한다. +- [ ] idempotent 재호출 테스트가 여전히 통과하도록 exact promoted legacy instance는 enabled 값까지 동일할 때만 허용한다. +- [ ] `go test -count=1 ./packages/go/config ./apps/edge/internal/node ./apps/edge/internal/transport ./apps/edge/internal/edgecmd`, `go test -count=1 ./...`, `go build ./apps/edge/... ./apps/node/...`를 실행하고 실제 stdout/stderr를 기록한다. +- [ ] 원격 field 환경이 준비되어 있으면 `/healthz`, `/v1/models`, inference target 두 개 호출, CLI target 호출이 같은 connected Node id/alias를 쓰는 증거를 수집한다. 준비되어 있지 않으면 `사용자 리뷰 요청`에 user-owned blocker와 재개 조건을 기록한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +### [REVIEW_REVIEW_TEST-1] Enabled mismatch conflict + +- 문제: same-name explicit instance가 disabled이면 enabled legacy adapter가 silently dropped될 수 있다. +- 해결 방법: `findOllamaInstance`/`findVllmInstance`로 찾은 explicit instance가 legacy promoted instance와 `Enabled`까지 정확히 일치하는지 비교한다. `Enabled`, `BaseURL`, `ContextSize`, `Endpoint` 중 하나라도 다르면 error를 반환한다. +- 수정 파일 및 체크리스트: + - [ ] `packages/go/config/config.go`: same-name legacy/explicit conflict 비교에 `Enabled`를 포함한다. + - [ ] `packages/go/config/config_test.go`: disabled explicit same-name Ollama/Vllm conflict 테스트를 추가한다. + - [ ] 필요 시 `apps/edge/internal/node/store_test.go`의 payload 유지 테스트를 새 계약과 맞춰 보강한다. +- 테스트 작성: 필수. disabled same-name conflict가 이번 Required issue의 본체다. +- 중간 검증: + - `go test -count=1 ./packages/go/config ./apps/edge/internal/node ./apps/edge/internal/transport ./apps/edge/internal/edgecmd` + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `packages/go/config/config.go` | REVIEW_REVIEW_TEST-1 | +| `packages/go/config/config_test.go` | REVIEW_REVIEW_TEST-1 | +| `agent-task/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/CODE_REVIEW-cloud-G07.md` | REVIEW_REVIEW_TEST-1 | + +## 최종 검증 + +```bash +go test -count=1 ./packages/go/config ./apps/edge/internal/node ./apps/edge/internal/transport ./apps/edge/internal/edgecmd +go test -count=1 ./... +go build ./apps/edge/... ./apps/node/... +curl -fsS http:///healthz +curl -fsS http:///v1/models +curl -fsS http:///v1/chat/completions -H 'Content-Type: application/json' -d '{"model":"","messages":[{"role":"user","content":"ping"}]}' +curl -fsS http:///v1/chat/completions -H 'Content-Type: application/json' -d '{"model":"","messages":[{"role":"user","content":"ping"}]}' +``` + +CLI target 호출은 프로젝트의 최신 Edge command endpoint 또는 `iop-edge` command로 실행하고, 실제 명령과 출력을 `CODE_REVIEW-cloud-G07.md`에 기록한다. 원격 환경이 사용자 소유 prerequisite 때문에 불가하면 실행하지 못한 field smoke 명령과 정확한 blocker를 `사용자 리뷰 요청`에 기록한다. diff --git a/agent-task/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/CODE_REVIEW-cloud-G07.md b/agent-task/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/CODE_REVIEW-cloud-G07.md deleted file mode 100644 index 87dd444..0000000 --- a/agent-task/m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke/CODE_REVIEW-cloud-G07.md +++ /dev/null @@ -1,112 +0,0 @@ - - -# Code Review Reference - TEST - -> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.** -> Record the exact remote/local environment, commands, stdout/stderr, and any artifact paths. -> If blocked by user-owned server, node, token, secret, or model endpoint preparation, fill `사용자 리뷰 요청` and stop. -> Do not ask the user directly. - -## 개요 - -date=2026-06-10 -task=m-node-multi-target-serving-foundation/08+03,04,05,06,07_field_smoke, plan=0, tag=TEST - -## Roadmap Targets - -- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/node-multi-target-serving-foundation.md` -- Task ids: - - `field-smoke`: split-host smoke에서 하나의 연결된 Node가 최소 두 개의 inference model target과 하나의 CLI target을 같은 Edge-Node 연결로 호출할 수 있음을 확인한다. -- Completion mode: check-on-pass - -## 이 파일을 읽는 리뷰 에이전트에게 - -> **[REVIEW AGENT ONLY]** 종결 절차는 코드리뷰 에이전트 전용이다. - -smoke 증거가 같은 Edge-Node 연결과 같은 Node id를 가리키는지 확인한다. 환경 blocker가 repo-owned인지 user-owned인지 구분한다. - -## 구현 항목별 완료 여부 - -| 항목 | 완료 여부 | -|------|---------| -| [TEST-1] Split Host Multi Target Smoke | [ ] | - -## 구현 체크리스트 - -- [ ] split-host smoke 사전 조건과 실제 실행 config를 기록한다. -- [ ] Edge `/v1/models`가 두 inference model target을 노출하는 증거를 수집한다. -- [ ] 같은 연결된 Node를 통해 inference model target 두 개를 각각 호출하는 증거를 수집한다. -- [ ] 같은 연결된 Node를 통해 CLI target 하나를 호출하는 증거를 수집한다. -- [ ] 실패 시 repo-owned fix와 user-owned prerequisite을 구분해 기록한다. -- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. - -## 코드리뷰 전용 체크리스트 - -- [ ] `코드리뷰 결과`에 판정을 append한다. -- [ ] active `CODE_REVIEW-cloud-G07.md`를 `code_review_cloud_G07_N.log`로 아카이브한다. -- [ ] active `PLAN-cloud-G07.md`를 `plan_cloud_G07_M.log`로 아카이브한다. -- [ ] PASS이면 `complete.log` 작성 후 archive 이동한다. -- [ ] WARN/FAIL이면 후속 상태 또는 `USER_REVIEW.md`를 작성한다. - -## 계획 대비 변경 사항 - -_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._ - -## 주요 설계 결정 - -_구현 에이전트가 주요 설계 결정 사항을 기록한다._ - -## 사용자 리뷰 요청 - -_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._ - -- 상태: 없음 -- 사유 유형: 없음 -- 결정 필요: 없음 -- 차단 근거: 없음 -- 실행한 검증/명령: 없음 -- 자동 후속 불가 이유: 없음 -- 재개 조건: 없음 - -## 리뷰어를 위한 체크포인트 - -- `/v1/models`에 두 inference route가 실제로 보인다. -- 두 inference 호출과 CLI 호출이 같은 connected Node id/alias를 사용한다. -- secret/token 값이 문서에 노출되지 않았다. -- 실패가 있으면 repo-owned follow-up과 user-owned blocker가 분리되어 있다. - -## 검증 결과 - -### TEST-1 중간 검증 - -```bash -$ go test -count=1 ./... -(output) -``` - -```bash -$ curl -fsS http:///healthz -(output) -``` - -```bash -$ curl -fsS http:///v1/models -(output) -``` - -### 최종 검증 - -```bash -$ curl -fsS http:///v1/chat/completions -H 'Content-Type: application/json' -d '{"model":"","messages":[{"role":"user","content":"ping"}]}' -(output) -``` - -```bash -$ curl -fsS http:///v1/chat/completions -H 'Content-Type: application/json' -d '{"model":"","messages":[{"role":"user","content":"ping"}]}' -(output) -``` - -```bash -$ -(output) -``` diff --git a/apps/edge/internal/node/store.go b/apps/edge/internal/node/store.go index 21e83e1..4a2656c 100644 --- a/apps/edge/internal/node/store.go +++ b/apps/edge/internal/node/store.go @@ -95,13 +95,17 @@ func LoadFromConfig(defs []config.NodeDefinition) (*NodeStore, error) { if agentKind == "" { agentKind = config.AgentKindGenericNode } + adapters := d.Adapters + if err := config.NormalizeAdapters(&adapters); err != nil { + return nil, fmt.Errorf("node[%d] alias=%q: adapters: %w", i, d.Alias, err) + } s.Add(&NodeRecord{ ID: nodeID, Alias: d.Alias, Token: d.Token, AgentKind: agentKind, Index: i, - Adapters: d.Adapters, + Adapters: adapters, Runtime: d.Runtime, }) } diff --git a/apps/edge/internal/node/store_test.go b/apps/edge/internal/node/store_test.go index a50dbbf..48c7c23 100644 --- a/apps/edge/internal/node/store_test.go +++ b/apps/edge/internal/node/store_test.go @@ -143,6 +143,72 @@ func TestLoadFromConfig_DuplicateTokenWithAgentKind(t *testing.T) { } } +func TestLoadFromConfig_LegacyOllamaInPayload(t *testing.T) { + store, err := edgenode.LoadFromConfig([]config.NodeDefinition{ + { + ID: "node-ollama", + Alias: "ollama-node", + Token: "token-ollama", + Adapters: config.AdaptersConf{ + Ollama: config.OllamaConf{Enabled: true, BaseURL: "http://127.0.0.1:11434"}, + }, + }, + }) + if err != nil { + t.Fatalf("load from config: %v", err) + } + rec, ok := store.FindByToken("token-ollama") + if !ok { + t.Fatal("expected record for token-ollama") + } + payload, err := edgenode.BuildConfigPayload(rec) + if err != nil { + t.Fatalf("build config payload: %v", err) + } + var found bool + for _, a := range payload.GetAdapters() { + if a.GetType() == "ollama" && a.GetOllama().GetBaseUrl() == "http://127.0.0.1:11434" { + found = true + } + } + if !found { + t.Fatalf("expected ollama adapter in payload with correct base_url, got %v", payload.GetAdapters()) + } +} + +func TestLoadFromConfig_LegacyVllmInPayload(t *testing.T) { + store, err := edgenode.LoadFromConfig([]config.NodeDefinition{ + { + ID: "node-vllm", + Alias: "vllm-node", + Token: "token-vllm", + Adapters: config.AdaptersConf{ + Vllm: config.VllmConf{Enabled: true, Endpoint: "http://127.0.0.1:8000"}, + }, + }, + }) + if err != nil { + t.Fatalf("load from config: %v", err) + } + rec, ok := store.FindByToken("token-vllm") + if !ok { + t.Fatal("expected record for token-vllm") + } + payload, err := edgenode.BuildConfigPayload(rec) + if err != nil { + t.Fatalf("build config payload: %v", err) + } + var found bool + for _, a := range payload.GetAdapters() { + if a.GetType() == "vllm" && a.GetVllm().GetEndpoint() == "http://127.0.0.1:8000" { + found = true + } + } + if !found { + t.Fatalf("expected vllm adapter in payload with correct endpoint, got %v", payload.GetAdapters()) + } +} + func TestLoadFromConfig_AutoIDUnique(t *testing.T) { store, err := edgenode.LoadFromConfig([]config.NodeDefinition{ {Alias: "alpha", Token: "token-alpha"}, diff --git a/apps/edge/internal/transport/server_test.go b/apps/edge/internal/transport/server_test.go index 23d79a0..0e12049 100644 --- a/apps/edge/internal/transport/server_test.go +++ b/apps/edge/internal/transport/server_test.go @@ -271,8 +271,12 @@ func TestBuildConfigPayload_CLICompletionMarker(t *testing.T) { func TestBuildConfigPayload_OllamaVllmOneof(t *testing.T) { rec := &edgenode.NodeRecord{ Adapters: config.AdaptersConf{ - Ollama: config.OllamaConf{Enabled: true, BaseURL: "http://localhost:11434"}, - Vllm: config.VllmConf{Enabled: true, Endpoint: "http://localhost:8000"}, + OllamaInstances: []config.OllamaInstanceConf{ + {Name: "ollama", Enabled: true, BaseURL: "http://localhost:11434"}, + }, + VllmInstances: []config.VllmInstanceConf{ + {Name: "vllm", Enabled: true, Endpoint: "http://localhost:8000"}, + }, }, } payload, err := edgenode.BuildConfigPayload(rec) diff --git a/packages/go/config/config.go b/packages/go/config/config.go index beb1683..d3b798d 100644 --- a/packages/go/config/config.go +++ b/packages/go/config/config.go @@ -300,25 +300,37 @@ func LoadEdge(cfgFile string) (*EdgeConfig, error) { return &cfg, nil } -// normalizeAdapters promotes legacy single-instance Ollama/Vllm fields into the +// NormalizeAdapters promotes legacy single-instance Ollama/Vllm fields into the // typed instance slices and validates that all instance names are unique. +func NormalizeAdapters(a *AdaptersConf) error { + return normalizeAdapters(a) +} + func normalizeAdapters(a *AdaptersConf) error { if a.Ollama.Enabled { - legacy := OllamaInstanceConf{ - Name: "ollama", - Enabled: a.Ollama.Enabled, - BaseURL: a.Ollama.BaseURL, - ContextSize: a.Ollama.ContextSize, + existing := findOllamaInstance(a.OllamaInstances, "ollama") + if existing == nil { + a.OllamaInstances = append([]OllamaInstanceConf{{ + Name: "ollama", + Enabled: a.Ollama.Enabled, + BaseURL: a.Ollama.BaseURL, + ContextSize: a.Ollama.ContextSize, + }}, a.OllamaInstances...) + } else if !existing.Enabled || existing.BaseURL != a.Ollama.BaseURL || existing.ContextSize != a.Ollama.ContextSize { + return fmt.Errorf("ollama: legacy field conflicts with explicit instance %q: enabled/base_url/context_size mismatch", "ollama") } - a.OllamaInstances = append([]OllamaInstanceConf{legacy}, a.OllamaInstances...) } if a.Vllm.Enabled { - legacy := VllmInstanceConf{ - Name: "vllm", - Enabled: a.Vllm.Enabled, - Endpoint: a.Vllm.Endpoint, + existing := findVllmInstance(a.VllmInstances, "vllm") + if existing == nil { + a.VllmInstances = append([]VllmInstanceConf{{ + Name: "vllm", + Enabled: a.Vllm.Enabled, + Endpoint: a.Vllm.Endpoint, + }}, a.VllmInstances...) + } else if !existing.Enabled || existing.Endpoint != a.Vllm.Endpoint { + return fmt.Errorf("vllm: legacy field conflicts with explicit instance %q: enabled/endpoint mismatch", "vllm") } - a.VllmInstances = append([]VllmInstanceConf{legacy}, a.VllmInstances...) } if err := checkUniqueNames("ollama_instances", func(i int) string { return a.OllamaInstances[i].Name }, len(a.OllamaInstances)); err != nil { @@ -330,6 +342,24 @@ func normalizeAdapters(a *AdaptersConf) error { return nil } +func findOllamaInstance(instances []OllamaInstanceConf, name string) *OllamaInstanceConf { + for i := range instances { + if instances[i].Name == name { + return &instances[i] + } + } + return nil +} + +func findVllmInstance(instances []VllmInstanceConf, name string) *VllmInstanceConf { + for i := range instances { + if instances[i].Name == name { + return &instances[i] + } + } + return nil +} + // validateOpenAIRoutes rejects duplicate and empty model ids in the route catalog. func validateOpenAIRoutes(routes []OpenAIRouteEntry) error { seen := make(map[string]struct{}, len(routes)) diff --git a/packages/go/config/config_test.go b/packages/go/config/config_test.go index 9061402..bfa6daf 100644 --- a/packages/go/config/config_test.go +++ b/packages/go/config/config_test.go @@ -1154,6 +1154,92 @@ openai: } } +// NormalizeAdapters regression tests + +func TestNormalizeAdapters_OllamaIdempotent(t *testing.T) { + a := config.AdaptersConf{ + Ollama: config.OllamaConf{Enabled: true, BaseURL: "http://127.0.0.1:11434", ContextSize: 4096}, + } + if err := config.NormalizeAdapters(&a); err != nil { + t.Fatalf("first normalize: %v", err) + } + if len(a.OllamaInstances) != 1 { + t.Fatalf("expected 1 instance after first normalize, got %d", len(a.OllamaInstances)) + } + if err := config.NormalizeAdapters(&a); err != nil { + t.Fatalf("second normalize (idempotent): %v", err) + } + if len(a.OllamaInstances) != 1 { + t.Fatalf("expected still 1 instance after second normalize, got %d", len(a.OllamaInstances)) + } +} + +func TestNormalizeAdapters_VllmIdempotent(t *testing.T) { + a := config.AdaptersConf{ + Vllm: config.VllmConf{Enabled: true, Endpoint: "http://127.0.0.1:8000"}, + } + if err := config.NormalizeAdapters(&a); err != nil { + t.Fatalf("first normalize: %v", err) + } + if len(a.VllmInstances) != 1 { + t.Fatalf("expected 1 instance after first normalize, got %d", len(a.VllmInstances)) + } + if err := config.NormalizeAdapters(&a); err != nil { + t.Fatalf("second normalize (idempotent): %v", err) + } + if len(a.VllmInstances) != 1 { + t.Fatalf("expected still 1 instance after second normalize, got %d", len(a.VllmInstances)) + } +} + +func TestNormalizeAdapters_OllamaConflictErrors(t *testing.T) { + a := config.AdaptersConf{ + Ollama: config.OllamaConf{Enabled: true, BaseURL: "http://127.0.0.1:11434"}, + OllamaInstances: []config.OllamaInstanceConf{ + {Name: "ollama", Enabled: true, BaseURL: "http://other-host:11434"}, + }, + } + if err := config.NormalizeAdapters(&a); err == nil { + t.Fatal("expected error for conflicting legacy ollama and explicit instance with same name") + } +} + +func TestNormalizeAdapters_VllmConflictErrors(t *testing.T) { + a := config.AdaptersConf{ + Vllm: config.VllmConf{Enabled: true, Endpoint: "http://127.0.0.1:8000"}, + VllmInstances: []config.VllmInstanceConf{ + {Name: "vllm", Enabled: true, Endpoint: "http://other-host:8000"}, + }, + } + if err := config.NormalizeAdapters(&a); err == nil { + t.Fatal("expected error for conflicting legacy vllm and explicit instance with same name") + } +} + +func TestNormalizeAdapters_OllamaDisabledSameNameErrors(t *testing.T) { + a := config.AdaptersConf{ + Ollama: config.OllamaConf{Enabled: true, BaseURL: "http://127.0.0.1:11434"}, + OllamaInstances: []config.OllamaInstanceConf{ + {Name: "ollama", Enabled: false, BaseURL: "http://127.0.0.1:11434"}, + }, + } + if err := config.NormalizeAdapters(&a); err == nil { + t.Fatal("expected error for legacy enabled ollama conflicting with same-name disabled explicit instance") + } +} + +func TestNormalizeAdapters_VllmDisabledSameNameErrors(t *testing.T) { + a := config.AdaptersConf{ + Vllm: config.VllmConf{Enabled: true, Endpoint: "http://127.0.0.1:8000"}, + VllmInstances: []config.VllmInstanceConf{ + {Name: "vllm", Enabled: false, Endpoint: "http://127.0.0.1:8000"}, + }, + } + if err := config.NormalizeAdapters(&a); err == nil { + t.Fatal("expected error for legacy enabled vllm conflicting with same-name disabled explicit instance") + } +} + func TestLoadEdge_ControlPlaneOverride(t *testing.T) { dir := t.TempDir() f := filepath.Join(dir, "edge.yaml")