diff --git a/Makefile b/Makefile index 4292489..23396dd 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: all build build-local build-edge build-edge-host build-node build-node-target build-node-targets pack-node-target pack-edge archive-edge tidy test test-e2e test-control-plane-edge-wire test-openai-ollama proto proto-dart client-test client-build-web clean +.PHONY: all build build-local build-edge build-edge-host build-node build-node-target build-node-targets pack-node-target pack-edge archive-edge tidy test test-e2e test-control-plane-edge-wire test-openai-ollama test-openai-lemonade proto proto-dart client-test client-build-web clean GOFLAGS ?= -trimpath BUILD_DIR ?= build @@ -88,6 +88,9 @@ test-control-plane-edge-wire: test-openai-ollama: ./scripts/e2e-openai-ollama.sh +test-openai-lemonade: + ./scripts/e2e-openai-lemonade.sh + # Requires: protoc + protoc-gen-go (go install google.golang.org/protobuf/cmd/protoc-gen-go@latest) proto: protoc \ diff --git a/agent-roadmap/phase/inference-provider-extension/PHASE.md b/agent-roadmap/phase/inference-provider-extension/PHASE.md index fb3862e..19c64d9 100644 --- a/agent-roadmap/phase/inference-provider-extension/PHASE.md +++ b/agent-roadmap/phase/inference-provider-extension/PHASE.md @@ -24,9 +24,9 @@ Ollama 경로가 안정화된 뒤, 그 결과를 기준선으로 삼아 Lemonade - 경로: `agent-roadmap/archive/phase/inference-provider-extension/milestones/provider-availability-capacity-queue-foundation.md` - 요약: NomadCode workspace 실행 계약이 닫힌 뒤 provider별 health/model probe 차이를 Node adapter 내부로 숨기고, Edge가 공통으로 볼 수 있는 최소 상태와 capacity/in-flight/queued snapshot, FIFO admission queue 기준선을 만든다. -- [계획] Lemonade provider 서빙 경로 추가 +- [검토중] Lemonade provider 서빙 경로 추가 - 경로: `agent-roadmap/phase/inference-provider-extension/milestones/lemonade-provider-serving-validation.md` - - 요약: Node provider 상태와 capacity queue 기반 위에서 Lemonade provider의 adapter/config/target/model 매핑과 모델 조회, non-streaming/streaming chat 경로를 검증한다. + - 요약: Node provider 상태와 capacity queue 기반 위에서 Lemonade provider의 adapter/config/target/model 매핑과 모델 조회, non-streaming/streaming chat 경로를 검증했고, 사용자 최종 확인과 archive 승인을 기다린다. - [계획] vLLM provider 서빙 경로 추가 - 경로: `agent-roadmap/phase/inference-provider-extension/milestones/vllm-provider-serving-validation.md` diff --git a/agent-roadmap/phase/inference-provider-extension/milestones/lemonade-provider-serving-validation.md b/agent-roadmap/phase/inference-provider-extension/milestones/lemonade-provider-serving-validation.md index f5ed9c6..681098e 100644 --- a/agent-roadmap/phase/inference-provider-extension/milestones/lemonade-provider-serving-validation.md +++ b/agent-roadmap/phase/inference-provider-extension/milestones/lemonade-provider-serving-validation.md @@ -13,7 +13,7 @@ Edge OpenAI-compatible 입력 표면에서 Lemonade target/model의 모델 조 ## 상태 -[계획] +[검토중] ## 승격 조건 @@ -21,10 +21,11 @@ Edge OpenAI-compatible 입력 표면에서 Lemonade target/model의 모델 조 ## 구현 잠금 -- 상태: 잠금 -- 결정 필요: 아래 체크리스트 +- 상태: 해제 +- 결정 필요: 없음 +- 해소된 결정: - [x] Lemonade provider를 독립 `lemonade` adapter로 둘지, OpenAI-compatible inference server 공통 adapter 계열로 둘지 결정한다. - - [ ] 실테스트에 사용할 Lemonade endpoint, served model name 또는 기준 model, 인증/헤더 필요 여부, streaming 지원 기대 동작을 확인한다. + - [x] 실테스트에 사용할 Lemonade endpoint, served model name 또는 기준 model, 인증/헤더 필요 여부, streaming 지원 기대 동작을 확인한다. ## 범위 @@ -40,22 +41,25 @@ Edge OpenAI-compatible 입력 표면에서 Lemonade target/model의 모델 조 ### Epic: [lemonade-provider] Lemonade Provider Serving Path - [x] [provider-boundary] Lemonade provider를 독립 adapter로 둘지 OpenAI-compatible inference server 공통 adapter로 둘지 결정 근거가 정리되어 있다. -- [ ] [config-contract] Lemonade endpoint, served model name 또는 model, auth/header, timeout, option passthrough 설정 계약이 정리되어 있다. -- [ ] [availability] Lemonade provider의 endpoint/model probe 결과가 Node 공통 상태 모델 `unknown`, `available`, `unavailable`로 노출되는 기준이 검증되어 있다. -- [ ] [models-chat] Edge OpenAI-compatible `/v1/models`와 non-streaming `/v1/chat/completions`가 Lemonade provider로 수렴하는 기준이 검증되어 있다. -- [ ] [streaming] streaming `/v1/chat/completions`에서 SSE chunk, finish reason, 종료 신호가 Lemonade provider 경로로 안정적으로 전달되는지 검증되어 있다. -- [ ] [field-smoke] split-host field smoke에서 Lemonade target/model을 선택해 모델 조회와 chat 왕복이 검증되어 있다. -- [ ] [follow-up-scope] 코드 수정이 필요한 항목은 이 Milestone의 추가 Task 또는 같은 Milestone task group의 후속 plan으로 정리되어 있다. +- [x] [config-contract] Lemonade endpoint, served model name 또는 model, auth/header, timeout, option passthrough 설정 계약이 정리되어 있다. +- [x] [availability] Lemonade provider의 endpoint/model probe 결과가 Node 공통 상태 모델 `unknown`, `available`, `unavailable`로 노출되는 기준이 검증되어 있다. +- [x] [models-chat] Edge OpenAI-compatible `/v1/models`와 non-streaming `/v1/chat/completions`가 Lemonade provider로 수렴하는 기준이 검증되어 있다. +- [x] [streaming] streaming `/v1/chat/completions`에서 SSE chunk, finish reason, 종료 신호가 Lemonade provider 경로로 안정적으로 전달되는지 검증되어 있다. +- [x] [field-smoke] split-host field smoke에서 Lemonade target/model을 선택해 모델 조회와 chat 왕복이 검증되어 있다. +- [x] [follow-up-scope] 코드 수정이 필요한 항목은 이 Milestone의 추가 Task 또는 같은 Milestone task group의 후속 plan으로 정리되어 있다. ## 완료 리뷰 -- 상태: 없음 -- 요청일: 없음 -- 완료 근거: 모든 기능 Task가 아직 충족되지 않았다. +- 상태: 요청됨 +- 요청일: 2026-06-15 +- 완료 근거: + - `01_config_contract`, `02+01_openai_compatible_adapter`, `03+02_field_smoke` 완료 로그의 `Roadmap Completion`이 모든 기능 Task id를 PASS로 보고했다. + - Lemonade real smoke는 local LAN Lemonade server 일반 포트 `13305`와 `Gemma-4-26B-A4B-it-GGUF` 기준으로 PASS했다. + - fake smoke, Makefile target, 관련 Go package tests, `git diff --check`가 PASS했다. - 리뷰 필요: - [ ] 사용자가 완료 결과를 확인했다 - [ ] archive 이동을 승인했다 -- 리뷰 코멘트: 없음 +- 리뷰 코멘트: 코드레벨 완료 검토에서 smoke assertion 보강만 직접 반영했고, 큰 후속 plan이 필요한 이슈는 발견되지 않았다. 모든 기능 Task가 충족되어 완료 후보 상태다. 사용자 최종 확인과 archive 승인 전까지 활성 Milestone 경로에 유지한다. ## 범위 제외 @@ -75,4 +79,4 @@ Edge OpenAI-compatible 입력 표면에서 Lemonade target/model의 모델 조 - 표준선(확정): Lemonade는 독립 `lemonade` adapter를 새로 만들기보다 OpenAI-compatible inference server 공통 adapter 계열로 둔다. 공식 Lemonade Server 문서가 `/v1/models`, `/v1/chat/completions`와 streaming SSE를 OpenAI-compatible 표면으로 제공하고, 현재 코드에도 `vllm` adapter가 같은 `/v1/models`/`/v1/chat/completions` 기준선을 이미 구현하고 있기 때문이다. - 선행 작업: OpenAI Workspace Agent Execution Contract, Node provider 상태와 Capacity Queue 기반 - 후속 작업: Lemonade provider 실테스트에서 확인된 serving 경로 안정화 보완 -- 확인 필요: 실테스트 endpoint/served model name 또는 model, 인증/헤더 필요 여부, streaming 기대 동작 +- 확인 완료: 실테스트 기준은 현재 host에서 접근 가능한 local LAN Lemonade server의 일반 포트 `13305`, served model `Gemma-4-26B-A4B-it-GGUF`, 인증/헤더 없음, streaming SSE 종료 신호 `data: [DONE]` 지원 PASS로 정리한다. endpoint 원문은 tracked roadmap에 기록하지 않는다. diff --git a/agent-task/archive/2026/06/m-lemonade-provider-serving-validation/03+02_field_smoke/USER_REVIEW.md b/agent-task/archive/2026/06/m-lemonade-provider-serving-validation/03+02_field_smoke/USER_REVIEW.md new file mode 100644 index 0000000..7897e36 --- /dev/null +++ b/agent-task/archive/2026/06/m-lemonade-provider-serving-validation/03+02_field_smoke/USER_REVIEW.md @@ -0,0 +1,68 @@ +# User Review Resolved - m-lemonade-provider-serving-validation/03+02_field_smoke + +## 요청 일시 + +2026-06-15 + +## 상태 + +RESOLVED + +## 사유 + +- 유형: environment-blocked +- 현재 리뷰 회차: 2 +- 최종 판정: PASS +- 요약: 사용자가 현재 호스트 기준 real Lemonade endpoint를 지정했고, `Gemma-4-26B-A4B-it-GGUF` 모델로 real mode field smoke를 실행해 통과했다. + +## 루프 이력 + +| Plan | Review | Verdict | 메모 | +|------|--------|---------|------| +| `agent-task/m-lemonade-provider-serving-validation/03+02_field_smoke/plan_cloud_G07_0.log` | `agent-task/m-lemonade-provider-serving-validation/03+02_field_smoke/code_review_cloud_G07_0.log` | FAIL | 구현 산출물과 검증 출력이 없어 후속 구현 계획을 생성했다. | +| `agent-task/m-lemonade-provider-serving-validation/03+02_field_smoke/plan_cloud_G07_1.log` | `agent-task/m-lemonade-provider-serving-validation/03+02_field_smoke/code_review_cloud_G07_1.log` | FAIL | 사용자 소유 real Lemonade endpoint/model/auth 값이 없어 real mode field smoke가 차단됐다. | +| `USER_REVIEW.md` | user decision | PASS/RESOLVED | 사용자가 현재 호스트 기준 real Lemonade endpoint를 지정했고 real smoke가 PASS했다. | + +## 차단 근거 + +- 문제: `IOP_LEMONADE_MODE=real ./scripts/e2e-openai-lemonade.sh`는 real Lemonade endpoint/model/auth를 필요로 하지만 현재 환경에 해당 값이 없다. +- 현재 archive plan: `agent-task/m-lemonade-provider-serving-validation/03+02_field_smoke/plan_cloud_G07_1.log` +- 현재 archive review: `agent-task/m-lemonade-provider-serving-validation/03+02_field_smoke/code_review_cloud_G07_1.log` +- 검증 명령: `IOP_LEMONADE_MODE=real ./scripts/e2e-openai-lemonade.sh` +- 실제 출력: `./scripts/e2e-openai-lemonade.sh: line 69: IOP_LEMONADE_ENDPOINT: real mode requires IOP_LEMONADE_ENDPOINT` +- 차단 판단 근거: real endpoint/model/auth는 사용자 소유 외부 서비스와 secret에 해당한다. 후속 에이전트가 repo 내부 코드 변경이나 명령 재실행만으로 만들어낼 수 없다. + +## 해소 근거 + +- 사용자 결정: 현재 호스트 기준 real Lemonade endpoint를 사용해 접속한다. +- 모델 조회: default Lemonade port `13305`의 `/v1/models`가 응답했고 `Gemma-4-26B-A4B-it-GGUF` 모델을 확인했다. +- 검증 명령: `IOP_LEMONADE_MODE=real IOP_LEMONADE_ENDPOINT= IOP_LEMONADE_MODEL=Gemma-4-26B-A4B-it-GGUF ./scripts/e2e-openai-lemonade.sh` +- 실제 출력: + ```text + [openai-lemonade] real mode against external Lemonade endpoint (model=Gemma-4-26B-A4B-it-GGUF) + [openai-lemonade] OpenAI-compatible Lemonade serving test PASSED (mode=real). + ``` +- 해소 판정: real mode field smoke가 PASS했으므로 environment-blocked stop state를 완료로 해소한다. + +## 사용자 결정 필요 + +- [ ] 자동 follow-up plan/review를 계속 진행한다. +- [ ] 계획을 재작성한다. +- [x] 테스트 환경, secret, 외부 서비스, SDK, 장비 조건을 준비한 뒤 재시도한다. +- [ ] 작업 범위를 줄이거나 보류/폐기한다. + +## 재개 조건 + +- 충족됨: real Lemonade server endpoint와 served model name이 준비되어 있고 real smoke가 PASS했다. +- 인증값은 필요하지 않았다. +- secret/개인 endpoint 원문은 tracked 문서에 남기지 않았다. + +## 다음 실행 힌트 + +- 추가 자동 follow-up은 필요하지 않다. +- code-review가 `complete.log`를 작성하고 task directory를 archive로 이동한다. + +## 종료 규칙 + +- 사용자가 이 stop state를 완료/PASS로 해소하면 `USER_REVIEW.md`를 해소 상태로 갱신하고, `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다. +- 새 구현이 필요하면 `plan` 스킬이 `USER_REVIEW.md`를 `user_review_N.log`로 아카이브한 뒤 새 `PLAN-*-G??.md` / `CODE_REVIEW-*-G??.md`를 작성한다. diff --git a/agent-task/m-lemonade-provider-serving-validation/03+02_field_smoke/CODE_REVIEW-cloud-G07.md b/agent-task/archive/2026/06/m-lemonade-provider-serving-validation/03+02_field_smoke/code_review_cloud_G07_0.log similarity index 85% rename from agent-task/m-lemonade-provider-serving-validation/03+02_field_smoke/CODE_REVIEW-cloud-G07.md rename to agent-task/archive/2026/06/m-lemonade-provider-serving-validation/03+02_field_smoke/code_review_cloud_G07_0.log index fb11a6c..75461bc 100644 --- a/agent-task/m-lemonade-provider-serving-validation/03+02_field_smoke/CODE_REVIEW-cloud-G07.md +++ b/agent-task/archive/2026/06/m-lemonade-provider-serving-validation/03+02_field_smoke/code_review_cloud_G07_0.log @@ -63,16 +63,16 @@ task=m-lemonade-provider-serving-validation/03+02_field_smoke, plan=0, tag=TEST > **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. > 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다. -- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. -- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다. -- [ ] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G07_N.log`로 아카이브한다. -- [ ] active `PLAN-*-G??.md`를 `plan_cloud_G07_M.log`로 아카이브한다. -- [ ] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다. +- [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-lemonade-provider-serving-validation/03+02_field_smoke/`를 `agent-task/archive/YYYY/MM/m-lemonade-provider-serving-validation/03+02_field_smoke/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. - [ ] PASS이고 task group이 `m-`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다. - [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-lemonade-provider-serving-validation/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. -- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-cloud-G07.md`와 `CODE_REVIEW-cloud-G07.md`를 작성하고 `complete.log`를 작성하지 않는다. +- [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로 이동한다. @@ -155,3 +155,20 @@ $ git diff --check > **[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. + +## 코드리뷰 결과 + +- 종합 판정: FAIL +- 차원별 평가: + - correctness: Fail + - completeness: Fail + - test coverage: Fail + - API contract: Fail + - code quality: Pass + - plan deviation: Fail + - verification trust: Fail +- 발견된 문제: + - Required: `CODE_REVIEW-cloud-G07.md:48`의 구현 항목별 완료 여부가 모두 `[ ]`이고, `scripts/e2e-openai-lemonade.sh`도 존재하지 않습니다. 계획의 핵심 산출물인 Lemonade OpenAI-compatible smoke script를 추가하고 fake/real mode 검증 경로를 구현해야 합니다. + - Required: `CODE_REVIEW-cloud-G07.md:56` 및 `Makefile:1`, `Makefile:88` 기준으로 `test-openai-lemonade` target이 `.PHONY`와 Makefile target에 추가되지 않았습니다. 명시 실행 target을 추가하되 `test-e2e` 기본 dependency에는 넣지 않아야 합니다. + - Required: `CODE_REVIEW-cloud-G07.md:117`부터 `CODE_REVIEW-cloud-G07.md:150`까지 검증 출력이 `(output)` placeholder로 남아 있고, `CODE_REVIEW-cloud-G07.md:79`와 `CODE_REVIEW-cloud-G07.md:83`의 구현 소유 섹션도 placeholder 상태입니다. 계획된 명령을 실제 실행하고 stdout/stderr, 설계 결정, 계획 대비 변경 사항을 채워야 합니다. +- 다음 단계: WARN/FAIL follow-up으로 새 `PLAN-cloud-G07.md`와 `CODE_REVIEW-cloud-G07.md`를 작성한다. diff --git a/agent-task/archive/2026/06/m-lemonade-provider-serving-validation/03+02_field_smoke/code_review_cloud_G07_1.log b/agent-task/archive/2026/06/m-lemonade-provider-serving-validation/03+02_field_smoke/code_review_cloud_G07_1.log new file mode 100644 index 0000000..3b27668 --- /dev/null +++ b/agent-task/archive/2026/06/m-lemonade-provider-serving-validation/03+02_field_smoke/code_review_cloud_G07_1.log @@ -0,0 +1,214 @@ + + +# 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-15 +task=m-lemonade-provider-serving-validation/03+02_field_smoke, plan=1, tag=REVIEW_TEST + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/lemonade-provider-serving-validation.md` +- Task ids: + - `models-chat`: Edge OpenAI-compatible `/v1/models`와 non-streaming `/v1/chat/completions`가 Lemonade provider로 수렴하는 기준이 검증되어 있다. + - `streaming`: streaming `/v1/chat/completions`에서 SSE chunk, finish reason, 종료 신호가 Lemonade provider 경로로 안정적으로 전달되는지 검증되어 있다. + - `field-smoke`: split-host field smoke에서 Lemonade target/model을 선택해 모델 조회와 chat 왕복이 검증되어 있다. + - `follow-up-scope`: 코드 수정이 필요한 항목은 이 Milestone의 추가 Task 또는 같은 Milestone task group의 후속 plan으로 정리되어 있다. +- 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-lemonade-provider-serving-validation/03+02_field_smoke/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. `USER_REVIEW.md`가 사용자 결정으로 완료/PASS 해소되면 code-review가 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log` 작성 후 archive 이동한다. +4. PASS이고 task group이 `m-`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다. +5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [REVIEW_TEST-1] Lemonade smoke script | [x] | +| [REVIEW_TEST-2] Makefile target | [x] | +| [REVIEW_TEST-3] Verification and review artifact completion | [x] | + +## 구현 체크리스트 + +- [x] `scripts/e2e-openai-lemonade.sh`를 추가해 fake mode와 real mode에서 Edge/Node/OpenAI-compatible route를 검증한다. +- [x] `Makefile`에 명시 실행 target `test-openai-lemonade`를 추가하되 `test-e2e` 기본 dependency에는 넣지 않는다. +- [x] fake mode smoke, real mode smoke, 관련 Go package tests, `git diff --check`를 실행한다. real mode 환경값이 없으면 review stub의 `사용자 리뷰 요청`에 기록하고 PASS를 주장하지 않는다. +- [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-lemonade-provider-serving-validation/03+02_field_smoke/`를 `agent-task/archive/YYYY/MM/m-lemonade-provider-serving-validation/03+02_field_smoke/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. +- [ ] PASS이고 task group이 `m-`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다. +- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-lemonade-provider-serving-validation/`를 제거하거나, 남은 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로 이동한다. + +## 계획 대비 변경 사항 + +- 검증 명령은 계획/stub 고정 계약 그대로 실행했다(대체 없음). `IOP_LEMONADE_MODE=real ./scripts/e2e-openai-lemonade.sh`만 사용자 소유 외부 환경값 미준비로 끝까지 실행하지 못했고, 사유와 실제 출력을 `사용자 리뷰 요청`에 기록했다. +- 계획의 "Edge `/healthz`" 검증은 Edge OpenAI-compatible 서버(`apps/edge/internal/openai/routes.go:11`이 `/healthz` 핸들러를 등록)의 OpenAI listen 포트에서 `curl /healthz`로 구현했다. 별도 health 포트를 새로 만들지 않았다. +- model route lookup은 계획대로 `adapter: "openai_compat"`(type-name)을 썼다. node에는 `openai_compat_instances` 단일 인스턴스 `lemonade-local`만 등록되므로 registry legacy type lookup이 유일하게 해소된다. +- fake server는 `/v1/chat/completions`에서 `stream=true`만 허용하고 non-stream 요청은 400을 반환한다. Node `openai_compat` adapter가 provider로 항상 `stream=true`를 보내기 때문이며, Edge가 client에 대해 non-streaming/streaming 변환을 담당한다. + +## 주요 설계 결정 + +- **fake/real mode 구성**: `IOP_LEMONADE_MODE`(기본 `fake`). fake는 임시 Go OpenAI-compatible server가 `/v1/models`와 SSE `/v1/chat/completions`(reasoning_content → content 청크 2개 → finish_reason=stop → `[DONE]`, usage 포함)를 제공한다. real은 `IOP_LEMONADE_ENDPOINT`/`IOP_LEMONADE_MODEL`을 node adapter endpoint/target에 주입하고 fake server를 띄우지 않는다. +- **sentinel/검증 분리**: fake mode는 non-streaming 응답에서 `IOP_OPENAI_LEMONADE_OK`와 `reasoning_content`, streaming에서 `"content":"IOP_OPENAI_"`와 `data: [DONE]`을 고정 검증한다. real mode는 고정 sentinel 대신 non-empty `"content"`와 streaming `data: [DONE]`만 요구해 외부 모델 출력 차이를 허용한다. +- **endpoint normalization**: real mode endpoint는 root 또는 trailing `/v1` 둘 다 허용한다. 경로 중복(`/v1/v1`) 방지는 Node `openai_compat` adapter의 `joinOpenAIPath`가 담당하므로 script는 endpoint 원문을 그대로 config에 넣는다. +- **auth redaction**: `IOP_LEMONADE_AUTH_HEADER_NAME`/`IOP_LEMONADE_AUTH_HEADER_VALUE`가 모두 있을 때만 generated temp node config의 `headers:`에 기록한다. header value는 stdout/stderr나 tracked 파일에 출력하지 않는다. +- **cleanup**: trap EXIT에서 edge console에 `/exit`, node/fake server kill, temp dir 삭제를 처리한다. 기존 `scripts/e2e-openai-ollama.sh` 패턴을 따랐다. +- **Makefile**: `test-openai-lemonade`를 `.PHONY`와 명시 target에만 추가하고 `test-e2e` 기본 의존성에는 넣지 않았다(외부 Lemonade 의존성을 기본 smoke 흐름에 추가하지 않기 위함). + +## 사용자 리뷰 요청 + +- 상태: 있음 +- 사유 유형: 사용자 소유 외부 환경/서비스 준비 (real Lemonade endpoint/model/auth) +- 결정 필요: real mode field smoke를 검증하려면 사용자 소유 Lemonade 서버의 `IOP_LEMONADE_ENDPOINT`, `IOP_LEMONADE_MODEL`(필요 시 `IOP_LEMONADE_AUTH_HEADER_NAME`/`IOP_LEMONADE_AUTH_HEADER_VALUE`)을 제공받아야 한다. 이 값들은 secret/개인 endpoint이므로 사용자만 제공할 수 있다. +- 차단 근거: real mode는 외부 Lemonade 서버에 의존한다. 현재 환경에 endpoint/model/auth 값이 없어 `IOP_LEMONADE_MODE=real ./scripts/e2e-openai-lemonade.sh`가 required env 가드(`IOP_LEMONADE_ENDPOINT`)에서 즉시 종료한다. +- 실행한 검증/명령: + - `IOP_LEMONADE_MODE=real ./scripts/e2e-openai-lemonade.sh` → `./scripts/e2e-openai-lemonade.sh: line 69: IOP_LEMONADE_ENDPOINT: real mode requires IOP_LEMONADE_ENDPOINT` (exit 1). real Lemonade 왕복 미검증. + - fake mode smoke, `make test-openai-lemonade`, Go package tests, `git diff --check`는 모두 PASS(아래 검증 결과 참조). +- 자동 후속 불가 이유: real endpoint/model/auth는 사용자 소유 외부 자원이자 secret이다. 후속 에이전트가 명령 재실행이나 산출물 수집만으로 만들어낼 수 없다. +- 재개 조건: 사용자가 real Lemonade endpoint/model(및 필요 시 auth header)을 제공하면 `IOP_LEMONADE_MODE=real ... ./scripts/e2e-openai-lemonade.sh`를 실행해 real mode 증거를 채울 수 있다. 또는 code-review가 real field 검증을 별도 후속 task로 분리하기로 결정할 수 있다. + +## 리뷰어를 위한 체크포인트 + +- `scripts/e2e-openai-lemonade.sh`가 fake mode와 real mode 모두를 구현하고, fake mode가 Edge/Node/OpenAI-compatible route를 실제로 통과하는지 확인한다. +- `Makefile` target이 명시 실행용으로만 추가되고 `test-e2e` 기본 dependency에는 들어가지 않았는지 확인한다. +- script가 auth header value나 개인 endpoint를 tracked 파일 또는 review 출력에 노출하지 않았는지 확인한다. +- real mode evidence가 없으면 PASS가 아니라 사용자 리뷰 요청 또는 후속 plan으로 남았는지 확인한다. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +필수 규칙: +- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다. +- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다. +- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다. +- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다. +- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다. + +### REVIEW_TEST-1 중간 검증 +```bash +$ bash -n scripts/e2e-openai-lemonade.sh +(출력 없음, exit 0 — syntax OK) +$ ./scripts/e2e-openai-lemonade.sh +[openai-lemonade] OpenAI-compatible Lemonade serving test PASSED (mode=fake). +``` + +### REVIEW_TEST-2 중간 검증 +```bash +$ make test-openai-lemonade +./scripts/e2e-openai-lemonade.sh +[openai-lemonade] OpenAI-compatible Lemonade serving test PASSED (mode=fake). +``` + +### REVIEW_TEST-3 중간 검증 +```bash +$ IOP_LEMONADE_MODE=real ./scripts/e2e-openai-lemonade.sh +./scripts/e2e-openai-lemonade.sh: line 69: IOP_LEMONADE_ENDPOINT: real mode requires IOP_LEMONADE_ENDPOINT +# exit 1 — real Lemonade endpoint/model/auth는 사용자 소유 외부 환경이라 미준비. 사용자 리뷰 요청 참조. real mode PASS 미주장. + +$ go test -count=1 ./apps/edge/internal/openai ./apps/edge/internal/edgecmd ./apps/node/internal/adapters/openai_compat ./apps/node/internal/adapters +ok iop/apps/edge/internal/openai 1.509s +ok iop/apps/edge/internal/edgecmd 0.007s +ok iop/apps/node/internal/adapters/openai_compat 0.007s +ok iop/apps/node/internal/adapters 0.008s + +$ git diff --check +(출력 없음, exit 0 — whitespace error 없음) +``` + +### 최종 검증 +```bash +$ bash -n scripts/e2e-openai-lemonade.sh +(출력 없음, exit 0 — syntax OK) + +$ ./scripts/e2e-openai-lemonade.sh +[openai-lemonade] OpenAI-compatible Lemonade serving test PASSED (mode=fake). + +$ make test-openai-lemonade +./scripts/e2e-openai-lemonade.sh +[openai-lemonade] OpenAI-compatible Lemonade serving test PASSED (mode=fake). + +$ IOP_LEMONADE_MODE=real ./scripts/e2e-openai-lemonade.sh +./scripts/e2e-openai-lemonade.sh: line 69: IOP_LEMONADE_ENDPOINT: real mode requires IOP_LEMONADE_ENDPOINT +# exit 1 — 사용자 소유 외부 환경 미준비. 사용자 리뷰 요청 참조. + +$ go test -count=1 ./apps/edge/internal/openai ./apps/edge/internal/edgecmd ./apps/node/internal/adapters/openai_compat ./apps/node/internal/adapters +ok iop/apps/edge/internal/openai 1.509s +ok iop/apps/edge/internal/edgecmd 0.007s +ok iop/apps/node/internal/adapters/openai_compat 0.007s +ok iop/apps/node/internal/adapters 0.008s + +$ git diff --check +(출력 없음, exit 0 — whitespace error 없음) +``` + +--- + +> **[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: + +| Section | Owner | Note | +|---------|-------|------| +| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute these (archive, complete.log, and task-directory archive move are review-agent only) | +| Roadmap Targets | Fixed at stub creation from plan when present | Implementing agent must not modify; code-review copies it into `complete.log` as `Roadmap Completion` only on PASS | +| 구현 항목별 완료 여부 (item names) | Fixed at stub creation | Implementing agent checks `[ ]` -> `[x]` only | +| 구현 체크리스트 (item text/order) | Fixed at stub creation from plan | Implementing agent checks `[ ]` -> `[x]` only; final checkbox is mandatory before saving | +| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify or check this section | +| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholder text with actual content | +| 사용자 리뷰 요청 | Implementing agent | Keep `상태: 없음` unless user input is required to proceed; do not ask the user directly during implementation; when filled, include exact decision, evidence, commands/output, why automatic follow-up cannot resolve it, and resume condition | +| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Pre-filled from plan | +| 검증 결과 (section headings + commands) | Fixed at stub creation | Implementing agent fills in command output only; command changes require a `계획 대비 변경 사항` entry | +| 코드리뷰 결과 | Review agent appends | Not included in stub | + +## 코드리뷰 결과 + +- 종합 판정: FAIL +- 차원별 평가: + - correctness: Pass + - completeness: Fail + - test coverage: Fail + - API contract: Pass + - code quality: Pass + - plan deviation: Pass + - verification trust: Fail +- 발견된 문제: + - Required: `CODE_REVIEW-cloud-G07.md:95`의 사용자 리뷰 요청대로 real Lemonade field smoke가 미검증 상태입니다. `CODE_REVIEW-cloud-G07.md:140`의 `IOP_LEMONADE_MODE=real ./scripts/e2e-openai-lemonade.sh`는 `IOP_LEMONADE_ENDPOINT` 미설정으로 exit 1이며, Roadmap Targets의 `field-smoke`/real provider evidence를 충족하려면 사용자 소유 Lemonade endpoint/model/auth 값을 준비한 뒤 real mode smoke를 다시 실행해야 합니다. +- 다음 단계: USER_REVIEW로 전환한다. diff --git a/agent-task/archive/2026/06/m-lemonade-provider-serving-validation/03+02_field_smoke/complete.log b/agent-task/archive/2026/06/m-lemonade-provider-serving-validation/03+02_field_smoke/complete.log new file mode 100644 index 0000000..810897b --- /dev/null +++ b/agent-task/archive/2026/06/m-lemonade-provider-serving-validation/03+02_field_smoke/complete.log @@ -0,0 +1,52 @@ +# Complete - m-lemonade-provider-serving-validation/03+02_field_smoke + +## 완료 일시 + +2026-06-15 + +## 요약 + +Lemonade provider field smoke review completed after USER_REVIEW environment resolution with final verdict PASS. + +## 루프 이력 + +| Plan | Review | Verdict | 메모 | +|------|--------|---------|------| +| `agent-task/archive/2026/06/m-lemonade-provider-serving-validation/03+02_field_smoke/plan_cloud_G07_0.log` | `agent-task/archive/2026/06/m-lemonade-provider-serving-validation/03+02_field_smoke/code_review_cloud_G07_0.log` | FAIL | 구현 산출물과 검증 출력이 없어 후속 구현 계획을 생성했다. | +| `agent-task/archive/2026/06/m-lemonade-provider-serving-validation/03+02_field_smoke/plan_cloud_G07_1.log` | `agent-task/archive/2026/06/m-lemonade-provider-serving-validation/03+02_field_smoke/code_review_cloud_G07_1.log` | FAIL | fake smoke와 테스트는 통과했지만 real Lemonade endpoint/model 준비가 필요해 USER_REVIEW로 전환했다. | +| `USER_REVIEW.md` | user decision | PASS/RESOLVED | 사용자가 현재 호스트 기준 real Lemonade endpoint를 지정했고 real smoke가 PASS했다. | + +## 구현/정리 내용 + +- Added `scripts/e2e-openai-lemonade.sh` with fake and real Lemonade/OpenAI-compatible smoke paths. +- Added explicit `make test-openai-lemonade` target without adding real Lemonade dependency to `test-e2e`. +- Verified fake mode, Makefile target, related Go packages, whitespace, and real mode against the user-provided local Lemonade server. +- Completion audit tightened the smoke script to verify client-facing model exposure, alias-based CLI routing, bounded real chat prompts, and real reasoning-model payloads through either `content` or `reasoning_content`. + +## 최종 검증 + +- `bash -n scripts/e2e-openai-lemonade.sh` - PASS; no syntax output. +- `./scripts/e2e-openai-lemonade.sh` - PASS; output included `OpenAI-compatible Lemonade serving test PASSED (mode=fake).` +- `make test-openai-lemonade` - PASS; output included `OpenAI-compatible Lemonade serving test PASSED (mode=fake).` +- `go test -count=1 ./apps/edge/internal/openai ./apps/edge/internal/edgecmd ./apps/node/internal/adapters/openai_compat ./apps/node/internal/adapters` - PASS; all listed packages returned `ok`. +- `git diff --check` - PASS; no whitespace errors. +- `curl -fsS --connect-timeout 5 --max-time 20 /v1/models` - PASS; returned available Lemonade models including `Gemma-4-26B-A4B-it-GGUF`. +- `IOP_LEMONADE_MODE=real IOP_LEMONADE_ENDPOINT= IOP_LEMONADE_MODEL=Gemma-4-26B-A4B-it-GGUF ./scripts/e2e-openai-lemonade.sh` - PASS; output included `OpenAI-compatible Lemonade serving test PASSED (mode=real).` + +## Roadmap Completion + +- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/lemonade-provider-serving-validation.md` +- Completed task ids: + - `models-chat`: PASS; evidence=`agent-task/archive/2026/06/m-lemonade-provider-serving-validation/03+02_field_smoke/plan_cloud_G07_1.log`, `agent-task/archive/2026/06/m-lemonade-provider-serving-validation/03+02_field_smoke/code_review_cloud_G07_1.log`; verification=`./scripts/e2e-openai-lemonade.sh`, `IOP_LEMONADE_MODE=real IOP_LEMONADE_ENDPOINT= IOP_LEMONADE_MODEL=Gemma-4-26B-A4B-it-GGUF ./scripts/e2e-openai-lemonade.sh` + - `streaming`: PASS; evidence=`agent-task/archive/2026/06/m-lemonade-provider-serving-validation/03+02_field_smoke/plan_cloud_G07_1.log`, `agent-task/archive/2026/06/m-lemonade-provider-serving-validation/03+02_field_smoke/code_review_cloud_G07_1.log`; verification=`./scripts/e2e-openai-lemonade.sh`, `IOP_LEMONADE_MODE=real IOP_LEMONADE_ENDPOINT= IOP_LEMONADE_MODEL=Gemma-4-26B-A4B-it-GGUF ./scripts/e2e-openai-lemonade.sh` + - `field-smoke`: PASS; evidence=`agent-task/archive/2026/06/m-lemonade-provider-serving-validation/03+02_field_smoke/plan_cloud_G07_1.log`, `agent-task/archive/2026/06/m-lemonade-provider-serving-validation/03+02_field_smoke/code_review_cloud_G07_1.log`; verification=`IOP_LEMONADE_MODE=real IOP_LEMONADE_ENDPOINT= IOP_LEMONADE_MODEL=Gemma-4-26B-A4B-it-GGUF ./scripts/e2e-openai-lemonade.sh` + - `follow-up-scope`: PASS; evidence=`agent-task/archive/2026/06/m-lemonade-provider-serving-validation/03+02_field_smoke/USER_REVIEW.md`, `agent-task/archive/2026/06/m-lemonade-provider-serving-validation/03+02_field_smoke/complete.log`; verification=`USER_REVIEW.md resolved with real smoke PASS` +- Not completed task ids: 없음 + +## 잔여 Nit + +- 없음 + +## 후속 작업 + +- 없음 diff --git a/agent-task/m-lemonade-provider-serving-validation/03+02_field_smoke/PLAN-cloud-G07.md b/agent-task/archive/2026/06/m-lemonade-provider-serving-validation/03+02_field_smoke/plan_cloud_G07_0.log similarity index 100% rename from agent-task/m-lemonade-provider-serving-validation/03+02_field_smoke/PLAN-cloud-G07.md rename to agent-task/archive/2026/06/m-lemonade-provider-serving-validation/03+02_field_smoke/plan_cloud_G07_0.log diff --git a/agent-task/archive/2026/06/m-lemonade-provider-serving-validation/03+02_field_smoke/plan_cloud_G07_1.log b/agent-task/archive/2026/06/m-lemonade-provider-serving-validation/03+02_field_smoke/plan_cloud_G07_1.log new file mode 100644 index 0000000..a21f2d1 --- /dev/null +++ b/agent-task/archive/2026/06/m-lemonade-provider-serving-validation/03+02_field_smoke/plan_cloud_G07_1.log @@ -0,0 +1,232 @@ + + +# Plan - REVIEW_TEST + +## 이 파일을 읽는 구현 에이전트에게 + +구현 완료 전 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용, 설계 결정, 검증 출력으로 반드시 채운다. 구현 중 사용자 결정, 사용자 소유 외부 환경, secret, 서비스 준비, 또는 범위 충돌 없이는 안전하게 진행할 수 없으면 review stub의 `사용자 리뷰 요청` 섹션에 정확한 증거를 남기고 멈춘다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 공백은 사용자 리뷰 요청이 아니다. 최종 판정, archive, `complete.log` 작성은 code-review-skill 전용이다. + +## 배경 + +이전 리뷰는 구현 파일 변경이 없고 review stub의 구현 소유 섹션과 검증 출력이 비어 있어 `FAIL`로 종료됐다. 선행 OpenAI-compatible adapter subtask는 archive의 `complete.log`로 완료됐으므로, 이번 후속 작업은 Lemonade field smoke script, Makefile target, 실제 검증 증거를 완성하는 데 집중한다. + +## 사용자 리뷰 요청 흐름 + +구현 중 차단이 생기면 active review stub의 `사용자 리뷰 요청` 섹션에 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식으로 기록한다. 구현 에이전트가 채팅으로 직접 사용자에게 묻는 것은 금지되며, code-review가 요청의 타당성을 검증하고 실제 `USER_REVIEW.md` 작성 여부를 결정한다. 실제 Lemonade endpoint/model/auth 값이 준비되지 않아 real mode smoke를 실행할 수 없으면 이 섹션에 실행한 명령과 출력, 미실행 사유, 재개 조건을 기록하고 PASS를 주장하지 않는다. + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/lemonade-provider-serving-validation.md` +- Task ids: + - `models-chat`: Edge OpenAI-compatible `/v1/models`와 non-streaming `/v1/chat/completions`가 Lemonade provider로 수렴하는 기준이 검증되어 있다. + - `streaming`: streaming `/v1/chat/completions`에서 SSE chunk, finish reason, 종료 신호가 Lemonade provider 경로로 안정적으로 전달되는지 검증되어 있다. + - `field-smoke`: split-host field smoke에서 Lemonade target/model을 선택해 모델 조회와 chat 왕복이 검증되어 있다. + - `follow-up-scope`: 코드 수정이 필요한 항목은 이 Milestone의 추가 Task 또는 같은 Milestone task group의 후속 plan으로 정리되어 있다. +- Completion mode: check-on-pass + +## 분석 결과 + +### 읽은 파일 + +- `agent-ops/rules/project/rules.md` +- `agent-ops/rules/common/rules-roadmap.md` +- `agent-roadmap/current.md` +- `agent-ops/skills/common/router.md` +- `agent-ops/skills/common/code-review/SKILL.md` +- `agent-ops/skills/common/plan/SKILL.md` +- `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` +- `agent-ops/rules/project/domain/testing/rules.md` +- `agent-test/local/rules.md` +- `agent-test/local/testing-smoke.md` +- `agent-roadmap/phase/inference-provider-extension/milestones/lemonade-provider-serving-validation.md` +- `agent-task/archive/2026/06/m-lemonade-provider-serving-validation/02+01_openai_compatible_adapter/complete.log` +- `agent-task/m-lemonade-provider-serving-validation/03+02_field_smoke/plan_cloud_G07_0.log` +- `agent-task/m-lemonade-provider-serving-validation/03+02_field_smoke/code_review_cloud_G07_0.log` +- `Makefile` +- `scripts/e2e-openai-lemonade.sh` 존재 확인: missing + +### 테스트 환경 규칙 + +- `test_env=local`. +- `agent-test/local/rules.md`와 matched profile `agent-test/local/testing-smoke.md`를 읽었다. +- 테스트 도구 자체를 바꾸는 작업이므로 해당 도구를 직접 실행해야 한다. +- 적용 명령: `bash -n scripts/e2e-openai-lemonade.sh`, `./scripts/e2e-openai-lemonade.sh`, `make test-openai-lemonade`, real mode smoke, 관련 Go package tests, `git diff --check`. +- field/runtime 외부 endpoint evidence가 필요하면 사용자 소유 host 또는 원격 runner가 필요할 수 있다. real mode 환경값이 없으면 review stub의 사용자 리뷰 요청으로 멈춘다. +- secret, token, 개인 endpoint 원문은 tracked 파일과 최종 보고에 기록하지 않는다. + +### 테스트 커버리지 공백 + +- `scripts/e2e-openai-lemonade.sh`가 없어서 Lemonade/OpenAI-compatible adapter route fake/real smoke가 없다. +- `Makefile`에 `test-openai-lemonade` target이 없어 명시 실행 계약이 없다. +- 이전 review stub은 실제 stdout/stderr를 붙이지 않아 fake mode, real mode, Go package tests, whitespace 검증 신뢰성이 없다. + +### 심볼 참조 + +- rename/remove 없음. +- 새 script target `test-openai-lemonade` call site는 `Makefile`에 한정한다. + +### 분할 판단 + +- 이 후속 plan은 기존 split subtask `03+02_field_smoke` 안에서 이전 `FAIL` findings를 해소한다. +- predecessor `02`는 `agent-task/archive/2026/06/m-lemonade-provider-serving-validation/02+01_openai_compatible_adapter/complete.log`로 충족됐다. +- 새 split은 만들지 않는다. 남은 문제는 script, Makefile target, 검증 증거가 같은 field smoke 계약 안에서 함께 판정되어야 한다. + +### 범위 결정 근거 + +- Lemonade 서버 설치, 모델 다운로드, 운영 제품화는 제외한다. +- `make test-e2e` 기본 흐름에는 real Lemonade dependency를 추가하지 않는다. +- secret/header 값은 tracked script, config, docs, roadmap에 쓰지 않는다. +- smoke 중 발견된 provider 또는 OpenAI-compatible runtime 이슈가 script/config 소규모 보정으로 해결되지 않으면, 범위를 확장해 고치지 말고 review stub에 증거를 남겨 후속 plan으로 넘긴다. + +### 빌드 등급 + +- `cloud-G07`: shell smoke orchestration, process control, stdout/stderr evidence, external real mode gate가 핵심이다. + +## 구현 체크리스트 + +- [ ] `scripts/e2e-openai-lemonade.sh`를 추가해 fake mode와 real mode에서 Edge/Node/OpenAI-compatible route를 검증한다. +- [ ] `Makefile`에 명시 실행 target `test-openai-lemonade`를 추가하되 `test-e2e` 기본 dependency에는 넣지 않는다. +- [ ] fake mode smoke, real mode smoke, 관련 Go package tests, `git diff --check`를 실행한다. real mode 환경값이 없으면 review stub의 `사용자 리뷰 요청`에 기록하고 PASS를 주장하지 않는다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 의존 관계 및 구현 순서 + +- `03+02_field_smoke`의 predecessor `02`는 `agent-task/archive/2026/06/m-lemonade-provider-serving-validation/02+01_openai_compatible_adapter/complete.log`로 충족됐다. +- 먼저 `scripts/e2e-openai-lemonade.sh`를 구현하고 fake mode를 직접 통과시킨다. +- 그 다음 `Makefile` target을 추가하고 `make test-openai-lemonade`가 같은 script를 실행하는지 확인한다. +- 마지막으로 real mode와 Go package tests, `git diff --check` 결과를 review stub에 실제 출력으로 붙인다. + +## REVIEW_TEST-1 Lemonade smoke script + +### 문제 + +`scripts/e2e-openai-lemonade.sh`가 없고, 이전 review의 `code_review_cloud_G07_0.log`는 `CODE_REVIEW-cloud-G07.md:48`에서 모든 구현 항목이 미완료였다. Lemonade/OpenAI-compatible route를 fake/real mode로 검증하는 산출물이 없다. + +### 해결 방법 + +- 새 `scripts/e2e-openai-lemonade.sh`를 추가한다. +- 기본 `IOP_LEMONADE_MODE`는 `fake`다. fake mode는 임시 Go server가 `/v1/models`와 `/v1/chat/completions` non-streaming/streaming SSE를 제공하게 한다. +- real mode는 `IOP_LEMONADE_MODE=real`일 때 외부 Lemonade server를 사용한다. required env는 `IOP_LEMONADE_ENDPOINT`, `IOP_LEMONADE_MODEL`이다. endpoint는 root 또는 trailing `/v1` 둘 다 허용하되 Node adapter config에는 root 형태로 넣는다. +- optional auth는 `IOP_LEMONADE_AUTH_HEADER_NAME`과 `IOP_LEMONADE_AUTH_HEADER_VALUE`가 모두 있을 때 generated temp YAML `headers`에만 쓴다. script stdout/stderr에는 header value를 출력하지 않는다. +- generated Edge config는 `openai.model_routes`에 client-facing model `client-request-model`, `adapter: "openai_compat"`, `target: "$MODEL"`, `node: "test-node"`를 둔다. +- generated node adapter는 `openai_compat_instances` name `lemonade-local`, provider `lemonade`, endpoint, capacity/queue timeout을 사용한다. +- 검증 단계는 Edge `/healthz`, Edge `/v1/models`, Edge `/v1/chat/completions` non-streaming sentinel, streaming SSE content chunk와 `data: [DONE]`, `go run ./apps/edge/cmd/edge smoke openai --model client-request-model ...` 순서로 둔다. +- fake mode sentinel은 `IOP_OPENAI_LEMONADE_OK`로 고정한다. real mode는 empty response가 아니고 model route가 성공하면 통과하되, streaming `[DONE]`은 반드시 확인한다. + +### 수정 파일 및 체크리스트 + +- [ ] `scripts/e2e-openai-lemonade.sh`: 새 smoke script 추가. +- [ ] script 내부 temp config에는 secret value를 출력하지 않는다. +- [ ] script cleanup은 Edge/Node/fake server process와 temp dir를 정리한다. + +### 테스트 작성 + +- shell script 자체는 Go unit test 대신 `bash -n`, fake mode 실행, Makefile target 실행으로 검증한다. + +### 중간 검증 + +```bash +bash -n scripts/e2e-openai-lemonade.sh +./scripts/e2e-openai-lemonade.sh +``` + +기대 결과: syntax check PASS, fake mode smoke PASS. + +## REVIEW_TEST-2 Makefile target + +### 문제 + +`Makefile:1`의 `.PHONY`와 `Makefile:88` 주변 target 목록에 `test-openai-lemonade`가 없다. + +```makefile +1 .PHONY: all build build-local build-edge build-edge-host build-node build-node-target build-node-targets pack-node-target pack-edge archive-edge tidy test test-e2e test-control-plane-edge-wire test-openai-ollama proto proto-dart client-test client-build-web clean +... +88 test-openai-ollama: +89 ./scripts/e2e-openai-ollama.sh +``` + +### 해결 방법 + +- `.PHONY`에 `test-openai-lemonade`를 추가한다. +- `test-openai-lemonade` target은 `./scripts/e2e-openai-lemonade.sh`를 실행한다. +- `test-e2e`에는 추가하지 않는다. + +```makefile +.PHONY: ... test-openai-ollama test-openai-lemonade ... + +test-openai-ollama: + ./scripts/e2e-openai-ollama.sh + +test-openai-lemonade: + ./scripts/e2e-openai-lemonade.sh +``` + +### 수정 파일 및 체크리스트 + +- [ ] `Makefile`: `.PHONY`와 `test-openai-lemonade` target 추가. + +### 테스트 작성 + +- Makefile target은 별도 unit test 없이 `make test-openai-lemonade` 실행으로 검증한다. + +### 중간 검증 + +```bash +make test-openai-lemonade +``` + +기대 결과: fake mode smoke PASS. + +## REVIEW_TEST-3 Verification and review artifact completion + +### 문제 + +이전 review의 `code_review_cloud_G07_0.log`는 `CODE_REVIEW-cloud-G07.md:117`부터 `CODE_REVIEW-cloud-G07.md:150`까지 검증 출력이 `(output)` placeholder였고, `계획 대비 변경 사항`과 `주요 설계 결정`도 placeholder였다. 구현 완료 여부를 판단할 실제 stdout/stderr와 구현 설명이 없다. + +### 해결 방법 + +- 계획된 검증 명령을 실제 실행하고 `CODE_REVIEW-cloud-G07.md`의 `검증 결과`에 실제 stdout/stderr를 붙인다. +- `계획 대비 변경 사항`은 변경이 없으면 `없음`으로, 변경이 있으면 이유와 대체 명령까지 적는다. +- `주요 설계 결정`에는 fake/real mode 구성, endpoint normalization, auth redaction, cleanup 처리 결정을 짧게 기록한다. +- real mode 환경값이 없거나 endpoint/model/auth 준비가 안 되어 있으면 `사용자 리뷰 요청`에 실제 명령과 출력, 자동 후속 불가 이유, 재개 조건을 기록하고 구현 완료를 주장하지 않는다. + +### 수정 파일 및 체크리스트 + +- [ ] `CODE_REVIEW-cloud-G07.md`: 구현 항목별 완료 여부, 구현 체크리스트, 계획 대비 변경 사항, 주요 설계 결정, 사용자 리뷰 요청, 검증 결과를 실제 내용으로 채운다. + +### 테스트 작성 + +- 테스트 작성 없음. 이 항목은 검증 evidence와 review artifact 완성 항목이다. + +### 중간 검증 + +```bash +IOP_LEMONADE_MODE=real ./scripts/e2e-openai-lemonade.sh +go test -count=1 ./apps/edge/internal/openai ./apps/edge/internal/edgecmd ./apps/node/internal/adapters/openai_compat ./apps/node/internal/adapters +git diff --check +``` + +기대 결과: real mode 환경값이 있으면 real Lemonade smoke PASS. 없으면 사용자 리뷰 요청에 blocker evidence를 남기고 PASS 금지. Go package tests와 whitespace check는 PASS. + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `scripts/e2e-openai-lemonade.sh` | REVIEW_TEST-1, REVIEW_TEST-3 | +| `Makefile` | REVIEW_TEST-2 | +| `CODE_REVIEW-cloud-G07.md` | REVIEW_TEST-3 | + +## 최종 검증 + +```bash +bash -n scripts/e2e-openai-lemonade.sh +./scripts/e2e-openai-lemonade.sh +make test-openai-lemonade +IOP_LEMONADE_MODE=real ./scripts/e2e-openai-lemonade.sh +go test -count=1 ./apps/edge/internal/openai ./apps/edge/internal/edgecmd ./apps/node/internal/adapters/openai_compat ./apps/node/internal/adapters +git diff --check +``` + +기대 결과: 모든 명령이 exit 0. real mode 환경값이 없거나 endpoint/model/auth 준비가 안 되어 있으면 PASS 금지, `사용자 리뷰 요청` 작성. + +모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/scripts/e2e-openai-lemonade.sh b/scripts/e2e-openai-lemonade.sh new file mode 100755 index 0000000..595a38f --- /dev/null +++ b/scripts/e2e-openai-lemonade.sh @@ -0,0 +1,298 @@ +#!/usr/bin/env bash +set -euo pipefail + +# OpenAI-compatible Lemonade serving smoke. +# +# Verifies that the Edge OpenAI-compatible input surface converges onto the Node +# `openai_compat` adapter for a Lemonade-style provider: /v1/models lookup, +# non-streaming and streaming /v1/chat/completions, and the `edge smoke openai` +# command path. +# +# Modes (IOP_LEMONADE_MODE): +# fake (default) - a temporary Go OpenAI-compatible server stands in for +# Lemonade so the route can be checked with no external deps. +# real - an external Lemonade server is used. Required env: +# IOP_LEMONADE_ENDPOINT (root or trailing /v1 both allowed) +# IOP_LEMONADE_MODEL +# Optional auth (both must be set): +# IOP_LEMONADE_AUTH_HEADER_NAME +# IOP_LEMONADE_AUTH_HEADER_VALUE +# Auth header values are written only to the generated temp +# config and never printed to stdout/stderr. + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +TMP_DIR=$(mktemp -d) + +MODE="${IOP_LEMONADE_MODE:-fake}" + +EDGE_PID="" +NODE_PID="" +LEMONADE_PID="" +EDGE_FD_OPEN=0 + +cleanup() { + if [ "$EDGE_FD_OPEN" -eq 1 ]; then + { echo "/exit" >&3; } 2>/dev/null || true + exec 3>&- 2>/dev/null || true + fi + if [ -n "$EDGE_PID" ]; then wait "$EDGE_PID" 2>/dev/null || true; fi + if [ -n "$NODE_PID" ]; then kill "$NODE_PID" 2>/dev/null || true; fi + if [ -n "$LEMONADE_PID" ]; then kill "$LEMONADE_PID" 2>/dev/null || true; fi + if [ "${IOP_LEMONADE_KEEP_TMP:-0}" = "1" ]; then + echo "[openai-lemonade] keeping temp dir: $TMP_DIR" + return + fi + rm -rf "$TMP_DIR" 2>/dev/null || true +} +trap cleanup EXIT + +PORT=$((31000 + RANDOM % 5000)) +BOOTSTRAP_PORT=$((21000 + RANDOM % 5000)) +OPENAI_PORT=$((36000 + RANDOM % 5000)) +LEMONADE_PORT=$((41000 + RANDOM % 5000)) +EDGE_METRICS_PORT=$((46000 + RANDOM % 5000)) +NODE_METRICS_PORT=$((51000 + RANDOM % 5000)) + +wait_port() { + local host="$1" + local port="$2" + local label="$3" + local deadline=$((SECONDS + 20)) + while ! timeout 1 bash -c 'cat < /dev/null > /dev/tcp/"$1"/"$2"' _ "$host" "$port" 2>/dev/null; do + if (( SECONDS >= deadline )); then + echo "[openai-lemonade] $label did not open on $host:$port" + return 1 + fi + sleep 0.2 + done +} + +# Resolve provider endpoint and model based on mode. +if [ "$MODE" = "real" ]; then + : "${IOP_LEMONADE_ENDPOINT:?real mode requires IOP_LEMONADE_ENDPOINT}" + : "${IOP_LEMONADE_MODEL:?real mode requires IOP_LEMONADE_MODEL}" + NODE_ENDPOINT="$IOP_LEMONADE_ENDPOINT" + MODEL="$IOP_LEMONADE_MODEL" + echo "[openai-lemonade] real mode against external Lemonade endpoint (model=$MODEL)" +else + MODEL="fake-lemonade-model" + NODE_ENDPOINT="http://127.0.0.1:$LEMONADE_PORT" + + FAKE_LEMONADE="$TMP_DIR/fake_lemonade.go" + cat > "$FAKE_LEMONADE" <<'EOF' +package main + +import ( + "encoding/json" + "log" + "net/http" + "os" +) + +type chatRequest struct { + Model string `json:"model"` + Stream bool `json:"stream"` + Messages []struct { + Role string `json:"role"` + Content string `json:"content"` + } `json:"messages"` +} + +func main() { + if len(os.Args) < 3 { + log.Fatal("usage: fake_lemonade ") + } + model := os.Args[2] + mux := http.NewServeMux() + mux.HandleFunc("/v1/models", func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"object":"list","data":[{"id":"` + model + `"}]}`)) + }) + mux.HandleFunc("/v1/chat/completions", func(w http.ResponseWriter, r *http.Request) { + var req chatRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + if req.Model != model { + http.Error(w, "unexpected model: "+req.Model, http.StatusBadRequest) + return + } + if !req.Stream { + http.Error(w, "expected stream=true from openai_compat adapter", http.StatusBadRequest) + return + } + w.Header().Set("Content-Type", "text/event-stream") + flush := func() { + if f, ok := w.(http.Flusher); ok { + f.Flush() + } + } + _, _ = w.Write([]byte("data: " + `{"choices":[{"delta":{"reasoning_content":"thinking..."}}]}` + "\n\n")) + flush() + _, _ = w.Write([]byte("data: " + `{"choices":[{"delta":{"content":"IOP_OPENAI_"}}]}` + "\n\n")) + flush() + _, _ = w.Write([]byte("data: " + `{"choices":[{"delta":{"content":"LEMONADE_OK"},"finish_reason":"stop"}],"usage":{"prompt_tokens":5,"completion_tokens":2}}` + "\n\n")) + flush() + _, _ = w.Write([]byte("data: [DONE]\n\n")) + flush() + }) + log.Fatal(http.ListenAndServe(os.Args[1], mux)) +} +EOF + + go run "$FAKE_LEMONADE" "127.0.0.1:$LEMONADE_PORT" "$MODEL" > "$TMP_DIR/fake_lemonade.out" 2>&1 & + LEMONADE_PID=$! + wait_port 127.0.0.1 "$LEMONADE_PORT" "fake lemonade" +fi + +# Build the node openai_compat adapter block. Auth headers, when provided, are +# written only into the generated temp config (never echoed). +ADAPTER_BLOCK="$TMP_DIR/adapter_block.yaml" +{ + echo " openai_compat_instances:" + echo " - name: lemonade-local" + echo " enabled: true" + echo " provider: lemonade" + echo " endpoint: \"$NODE_ENDPOINT\"" + echo " capacity: 4" + echo " queue_timeout_ms: 5000" + if [ -n "${IOP_LEMONADE_AUTH_HEADER_NAME:-}" ] && [ -n "${IOP_LEMONADE_AUTH_HEADER_VALUE:-}" ]; then + echo " headers:" + echo " \"$IOP_LEMONADE_AUTH_HEADER_NAME\": \"$IOP_LEMONADE_AUTH_HEADER_VALUE\"" + fi +} > "$ADAPTER_BLOCK" + +EDGE_CONFIG="$TMP_DIR/edge.yaml" +NODE_CONFIG="$TMP_DIR/node.yaml" +cat > "$EDGE_CONFIG" < "$NODE_CONFIG" < "$EDGE_OUT" 2>&1 & +EDGE_PID=$! +exec 3> "$TMP_DIR/edge_fifo" +EDGE_FD_OPEN=1 + +wait_port 127.0.0.1 "$PORT" "edge node transport" +wait_port 127.0.0.1 "$OPENAI_PORT" "edge openai api" + +IOP_NODE_CONFIG="$NODE_CONFIG" "$REPO_ROOT/scripts/dev/node.sh" > "$NODE_OUT" 2>&1 & +NODE_PID=$! + +deadline=$((SECONDS + 30)) +while ! grep -q '\[node0-evt\] connected reason="registered"' "$EDGE_OUT"; do + if (( SECONDS >= deadline )); then + echo "[openai-lemonade] node registration timed out" + echo "=== EDGE OUTPUT ==="; cat "$EDGE_OUT" + echo "=== NODE OUTPUT ==="; cat "$NODE_OUT" + exit 1 + fi + sleep 0.2 +done + +# Edge health. +curl -fsS "http://127.0.0.1:$OPENAI_PORT/healthz" > /dev/null + +# /v1/models must surface both the client-facing model and provider target. +MODELS_OUT="$TMP_DIR/models.json" +curl -fsS "http://127.0.0.1:$OPENAI_PORT/v1/models" > "$MODELS_OUT" +grep -q "client-request-model" "$MODELS_OUT" +grep -q "$MODEL" "$MODELS_OUT" + +# Non-streaming chat completion. +CHAT_OUT="$TMP_DIR/chat.json" +curl -fsS \ + -H "Content-Type: application/json" \ + -d '{"model":"client-request-model","think":false,"options":{"max_tokens":32},"messages":[{"role":"user","content":"Reply with exactly: smoke token"}]}' \ + "http://127.0.0.1:$OPENAI_PORT/v1/chat/completions" > "$CHAT_OUT" +if [ "$MODE" = "fake" ]; then + grep -q "IOP_OPENAI_LEMONADE_OK" "$CHAT_OUT" + grep -q "reasoning_content" "$CHAT_OUT" +else + # real mode: reasoning models can emit a reasoning-only assistant payload + # when max_tokens is intentionally small for smoke speed. + grep -Eq '"(content|reasoning_content)":"[^"]' "$CHAT_OUT" +fi + +# Streaming chat completion: SSE content chunk(s) and a terminating [DONE]. +STREAM_OUT="$TMP_DIR/stream.txt" +curl -fsS -N \ + -H "Content-Type: application/json" \ + -d '{"model":"client-request-model","stream":true,"think":false,"options":{"max_tokens":32},"messages":[{"role":"user","content":"Reply with exactly: stream token"}]}' \ + "http://127.0.0.1:$OPENAI_PORT/v1/chat/completions" > "$STREAM_OUT" +if [ "$MODE" = "fake" ]; then + grep -q '"reasoning_content":"thinking..."' "$STREAM_OUT" + grep -q '"content":"IOP_OPENAI_"' "$STREAM_OUT" +else + grep -Eq '"(content|reasoning_content)":"[^"]' "$STREAM_OUT" +fi +grep -q 'data: \[DONE\]' "$STREAM_OUT" + +# Command-path smoke via the edge CLI. +SMOKE_OUT="$TMP_DIR/iop-edge-smoke-openai.txt" +(cd "$REPO_ROOT" && go run ./apps/edge/cmd/edge smoke openai \ + --model "client-request-model" \ + --base-url "http://127.0.0.1:$OPENAI_PORT" \ + --prompt "Reply with exactly: command smoke token" \ + --timeout 60s) > "$SMOKE_OUT" +grep -q "IOP Edge OpenAI Smoke Test SUCCESS!" "$SMOKE_OUT" + +if grep -i -E "node reported error|error run_id=|\[[^]]+-evt\] error|panic:" "$EDGE_OUT" "$NODE_OUT" >/dev/null; then + echo "[openai-lemonade] detected failure marker" + echo "=== EDGE OUTPUT ==="; cat "$EDGE_OUT" + echo "=== NODE OUTPUT ==="; cat "$NODE_OUT" + exit 1 +fi + +echo "[openai-lemonade] OpenAI-compatible Lemonade serving test PASSED (mode=$MODE)."