From d58d61810a948ba4ded109b0589fbee26a1c0fc2 Mon Sep 17 00:00:00 2001 From: toki Date: Tue, 14 Jul 2026 09:21:04 +0900 Subject: [PATCH] Remove cloud-G07 plan and code review files --- .../CODE_REVIEW-cloud-G07.md | 269 ------------------ .../PLAN-cloud-G07.md | 265 ----------------- 2 files changed, 534 deletions(-) delete mode 100644 agent-task/m-openai-compatible-provider-passthrough-contract-sync/CODE_REVIEW-cloud-G07.md delete mode 100644 agent-task/m-openai-compatible-provider-passthrough-contract-sync/PLAN-cloud-G07.md diff --git a/agent-task/m-openai-compatible-provider-passthrough-contract-sync/CODE_REVIEW-cloud-G07.md b/agent-task/m-openai-compatible-provider-passthrough-contract-sync/CODE_REVIEW-cloud-G07.md deleted file mode 100644 index d438b3f..0000000 --- a/agent-task/m-openai-compatible-provider-passthrough-contract-sync/CODE_REVIEW-cloud-G07.md +++ /dev/null @@ -1,269 +0,0 @@ - - -# Code Review Reference - 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 selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with linked evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Environment/secret/service blockers, generic scope changes, repeated failures, and evidence gaps that a follow-up agent can close are normal follow-up issues, not user-review blockers by themselves. -> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation; record only Milestone lock decisions in `사용자 리뷰 요청` and stop for code-review. -> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume. -> Follow the ownership table at the bottom of this file for which sections you own. - -## 개요 - -date=2026-07-14 -task=m-openai-compatible-provider-passthrough-contract-sync, plan=0, tag=TEST - -## Roadmap Targets - -- Milestone: `agent-roadmap/phase/routing-policy-model-orchestration/milestones/openai-compatible-provider-passthrough-contract-sync.md` -- Milestone link: [Milestone 문서](agent-roadmap/phase/routing-policy-model-orchestration/milestones/openai-compatible-provider-passthrough-contract-sync.md) -- Task ids: - - `devcorp-smoke`: dev-corp IOP 경유 Ornith Spark 요청에서 `chat_template_kwargs.enable_thinking=false`가 provider까지 전달된다. -- 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-openai-compatible-provider-passthrough-contract-sync/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. -4. PASS이고 task group이 `m-`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다. -5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다. - ---- - -## 구현 항목별 완료 여부 - -| 항목 | 완료 여부 | -|------|---------| -| [TEST-1] Dev-Corp Preflight | [ ] | -| [TEST-2] Think-Off Field Smoke | [ ] | - -## 구현 체크리스트 - -- [ ] TEST-1 dev-corp runner, provider, public Edge preflight를 실행하고 blocker 여부를 `CODE_REVIEW-cloud-G07.md`에 기록한다. -- [ ] TEST-2 direct Spark와 IOP 경유 `chat_template_kwargs.enable_thinking=false` 단일 smoke를 실행하고 first content latency, reasoning delta 관측, content summary를 `CODE_REVIEW-cloud-G07.md`에 기록한다. -- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. - -## 코드리뷰 전용 체크리스트 - -> **[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하는지 확인한다. -- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. -- [ ] PASS이면 active task 디렉터리 `agent-task/m-openai-compatible-provider-passthrough-contract-sync/`를 `agent-task/archive/YYYY/MM/m-openai-compatible-provider-passthrough-contract-sync/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. -- [ ] PASS이고 task group이 `m-`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다. -- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/{task_group}/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. -- [ ] 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가 연결된 Milestone 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다. - -## 계획 대비 변경 사항 - -- 계획된 검증 명령(TEST-1 preflight 2개, TEST-2 Python smoke)을 **대체하지 않았다**. 명령 자체는 고정 계약으로 유지했다. -- 다만 이 구현 세션은 dev-corp LAN 밖에 있어 유일한 접근 경로인 `ssh fe@172.24.63.178`(mac-mini runner)에 도달하지 못했다. 그 결과 TEST-1 preflight와 TEST-2 smoke를 runner에서 실행할 수 없어 **evidence 수집이 환경 blocker로 중단**되었다. -- 우회 경로 확인: `agent-test/dev-corp/{rules.md,inventory.yaml,*-smoke.md}`를 검색했으나 bastion/jump host/VPN/proxy 대체 경로는 문서화되어 있지 않다. 내부 Spark endpoint(`192.168.2.2:8003`, `192.168.2.4:8005`)와 secret 파일은 runner를 통해서만 접근 가능하다. -- 세션 도달성 실측(2026-07-13T22:07:02Z 기준, 아래 `검증 결과`에 원문 첨부): - - runner SSH `172.24.63.178:22` → `Connection timed out` (exit 255) - - 내부 Spark01 `192.168.2.2:8003/health` → timeout (exit 28, http 000) - - 내부 Spark02 `192.168.2.4:8005/health` → timeout (exit 28, http 000) - - 공개 Edge `https://digitalplatform.iop.ai.kr/v1/models` → http 401 (도달 가능, 인증 필요 = 정상). 단 direct Spark case를 대체하지 못하므로 S10 evidence로 불충분. -- 이 blocker는 stub 규칙(외부 환경/secret/서비스 준비 blocker)에 따라 **user-review 사유가 아니라 검증 blocker**로 분류하여 `검증 결과`에 기록한다. `사용자 리뷰 요청`은 `없음`으로 유지한다. - -## 주요 설계 결정 - -- repo source 파일 수정 없음. 이 plan은 evidence 수집 전용(TEST 등급)이며 코드/config/build 변경을 수행하지 않는다. -- 출력 위조 금지 원칙에 따라 TEST-2 smoke JSON summary를 임의 생성하지 않았다. runner 접근이 회복되는 후속 세션(dev-corp LAN 내부 실행)에서 TEST-1→TEST-2를 순서대로 재실행해야 evidence가 채워진다. - -## 사용자 리뷰 요청 - -_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._ - -- 상태: 없음 -- 사유 유형: 없음 -- 연결 대상: 없음 -- 결정 필요: 없음 -- 차단 근거: 없음 -- 실행한 검증/명령: 없음 -- 자동 후속 불가 이유: 없음 -- 재개 조건: 없음 - -## 리뷰어를 위한 체크포인트 - -- `Roadmap Targets`가 `devcorp-smoke` 하나만 가리키는지 확인한다. -- TEST-1 preflight가 secret 원문 없이 runner/provider/public Edge 상태를 증명하는지 확인한다. -- TEST-2 결과에서 direct Spark01/Spark02와 IOP public case가 모두 `ok: true`, `reasoning_seen: false`, `first_content_sec` 값을 갖는지 확인한다. -- 실패 또는 blocker가 user-review가 아니라 검증 blocker/follow-up으로 기록됐는지 확인한다. - -## 검증 결과 - -_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ - -필수 규칙: -- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다. -- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다. -- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다. -- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다. -- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다. - -> **환경 blocker (BLOCKED, not PASS/FAIL):** 이 구현 세션은 dev-corp LAN 밖이라 유일한 접근 경로인 `ssh fe@172.24.63.178`에 도달하지 못했다. TEST-1 preflight와 TEST-2 smoke는 runner에서 실행되지 못했다. 아래는 세션에서 실측한 도달성 probe 원문이다(위조 아님). runner 접근이 가능한 후속 세션에서 계약 명령을 그대로 재실행해야 한다. - -### TEST-1 중간 검증 -계약 명령(미실행 — runner 도달 불가): -```text -$ ssh fe@172.24.63.178 'cd /Users/fe/agent-work/iop-dev-corp && git status --short --branch && git log --oneline -1 && command -v python3 && command -v curl && test -r build/dev-corp-runtime/.secrets/openai_api_key' -NOT RUN — runner SSH 172.24.63.178:22 unreachable from this session (see probe below) -``` - -```text -$ ssh fe@172.24.63.178 'set -e -curl -fsS http://192.168.2.2:8003/health -curl -fsS http://192.168.2.2:8003/v1/models -curl -fsS http://192.168.2.4:8005/health -curl -fsS http://192.168.2.4:8005/v1/models -cd /Users/fe/agent-work/iop-dev-corp -OPENAI_API_KEY="$(cat build/dev-corp-runtime/.secrets/openai_api_key)" -curl -fsS -H "Authorization: Bearer ${OPENAI_API_KEY}" https://digitalplatform.iop.ai.kr/v1/models -' -NOT RUN — runner SSH unreachable; internal Spark endpoints only reachable via runner -``` - -도달성 probe 실측 (capture 2026-07-13T22:07:02Z): -```text -$ ssh -o BatchMode=yes -o ConnectTimeout=15 fe@172.24.63.178 'echo SSH_OK' -ssh: connect to host 172.24.63.178 port 22: Connection timed out -exit=255 - -$ curl -sS -m 10 -w "\nhttp=%{http_code} time_total=%{time_total}s\n" https://digitalplatform.iop.ai.kr/v1/models -{"error":{"type":"unauthorized","message":"unauthorized"}} -http=401 time_total=0.025189s # 공개 Edge 도달 가능(인증 필요=정상), direct Spark 대체 불가 - -$ curl -sS -m 8 -w "http=%{http_code}\n" http://192.168.2.2:8003/health # direct Spark01 -curl: (28) Connection timed out after 8008 milliseconds -http=000 exit=28 - -$ curl -sS -m 8 -w "http=%{http_code}\n" http://192.168.2.4:8005/health # direct Spark02 -curl: (28) Connection timed out after 8001 milliseconds -http=000 exit=28 -``` -판정: TEST-1 preflight **BLOCKED** — runner/secret/내부 provider 미도달. blocker 지점은 SSH 연결(172.24.63.178:22) 단계. - -### TEST-2 중간 검증 -```text -$ ssh fe@172.24.63.178 'cd /Users/fe/agent-work/iop-dev-corp && OPENAI_API_KEY="$(cat build/dev-corp-runtime/.secrets/openai_api_key)" python3 - <<'"'"'PY'"'"' -import json -import os -import time -import urllib.error -import urllib.request - -payload = { - "model": "ornith:35b", - "messages": [{"role": "user", "content": "Reply with one short sentence and do not include reasoning."}], - "stream": True, - "temperature": 0, - "max_tokens": 64, - "chat_template_kwargs": {"enable_thinking": False}, -} - -cases = [ - ("direct_spark01", "http://192.168.2.2:8003/v1/chat/completions", {}), - ("direct_spark02", "http://192.168.2.4:8005/v1/chat/completions", {}), - ("iop_public_ornith", "https://digitalplatform.iop.ai.kr/v1/chat/completions", {"Authorization": "Bearer " + os.environ["OPENAI_API_KEY"]}), -] - -def run_case(name, url, headers): - req_headers = {"Content-Type": "application/json"} - req_headers.update(headers) - req = urllib.request.Request(url, data=json.dumps(payload).encode("utf-8"), headers=req_headers, method="POST") - started = time.monotonic() - content = [] - reasoning_seen = False - first_content_sec = None - status = None - try: - with urllib.request.urlopen(req, timeout=180) as resp: - status = resp.status - for raw in resp: - line = raw.decode("utf-8", "replace").strip() - if not line.startswith("data: "): - continue - data = line[6:] - if data == "[DONE]": - break - obj = json.loads(data) - for choice in obj.get("choices", []): - delta = choice.get("delta") or {} - if delta.get("reasoning_content") or delta.get("reasoning") or delta.get("reasoning_text"): - reasoning_seen = True - piece = delta.get("content") - if piece: - if first_content_sec is None: - first_content_sec = round(time.monotonic() - started, 3) - content.append(piece) - except urllib.error.HTTPError as exc: - body = exc.read().decode("utf-8", "replace") - return {"name": name, "ok": False, "status": exc.code, "error_body": body[:500]} - except Exception as exc: - return {"name": name, "ok": False, "error": repr(exc)} - text = "".join(content).strip() - return { - "name": name, - "ok": status == 200 and first_content_sec is not None and not reasoning_seen, - "status": status, - "first_content_sec": first_content_sec, - "reasoning_seen": reasoning_seen, - "content_preview": text[:200], - } - -results = [run_case(*case) for case in cases] -print(json.dumps({"payload": {"model": payload["model"], "chat_template_kwargs": payload["chat_template_kwargs"]}, "results": results}, ensure_ascii=False, indent=2)) -if not all(item.get("ok") for item in results): - raise SystemExit(1) -PY' -NOT RUN — depends on runner (TEST-1) which is unreachable from this session. -# direct_spark01/direct_spark02 case는 내부 LAN(192.168.2.x)에서만 도달 가능하므로 runner 밖에서 대체 실행 불가. -# JSON summary는 위조하지 않는다. runner 접근 회복 후 재실행 필요. -``` -판정: TEST-2 think-off field smoke **BLOCKED** — 선행 TEST-1(runner 도달) 미충족. - -### 최종 검증 -```text -$ TEST-1 preflight commands + TEST-2 smoke command -BLOCKED — 이 세션에서 dev-corp runner(ssh fe@172.24.63.178)에 도달 불가하여 3개 계약 명령 모두 미실행. -세 case(direct_spark01, direct_spark02, iop_public_ornith) 중 direct Spark 2건은 내부 LAN 경유(runner)만 가능. -후속 조치: dev-corp LAN 내부에서 runner 접근이 가능한 세션이 TEST-1 → TEST-2를 순서대로 재실행하고 이 파일의 검증 결과를 채운다. -``` - ---- - -> **[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 | -| Roadmap Targets | Fixed at stub creation from plan | Implementing agent must not modify; code-review copies it into `complete.log` as `Roadmap Completion` only on PASS | -| 구현 항목별 완료 여부 | Implementing agent | Check completed items only | -| 구현 체크리스트 | Implementing agent | Check completed items only; final checkbox is mandatory | -| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify or check | -| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholder text with actual content | -| 사용자 리뷰 요청 | Implementing agent | Keep `상태: 없음` unless a selected Milestone lock decision blocks implementation | -| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Pre-filled from plan | -| 검증 결과 | Implementing agent | Fill actual command output | -| 코드리뷰 결과 | Review agent appends | Not included in stub | diff --git a/agent-task/m-openai-compatible-provider-passthrough-contract-sync/PLAN-cloud-G07.md b/agent-task/m-openai-compatible-provider-passthrough-contract-sync/PLAN-cloud-G07.md deleted file mode 100644 index de19a9c..0000000 --- a/agent-task/m-openai-compatible-provider-passthrough-contract-sync/PLAN-cloud-G07.md +++ /dev/null @@ -1,265 +0,0 @@ - - -# Implementation Plan - TEST - -## 이 파일을 읽는 구현 에이전트에게 - -`CODE_REVIEW-cloud-G07.md`의 구현 에이전트 소유 섹션을 반드시 채운다. 검증 명령을 실행하고 실제 출력 또는 저장한 출력 파일 경로를 붙인 뒤 active 파일을 유지한 채 리뷰 준비를 보고한다. 최종 archive, `complete.log`, roadmap 갱신은 code-review/runtime 책임이다. 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 막을 때만 review stub의 `사용자 리뷰 요청` 섹션을 채우며, 직접 사용자에게 질문하거나 `USER_REVIEW.md`를 만들지 않는다. 외부 환경, secret, 서비스 준비, 반복 실패, evidence 공백은 일반 follow-up 또는 검증 blocker로 기록한다. - -## 배경 - -현재 Milestone에서 local code/test로 `provider-error`와 `policy-priority`는 충족됐고, 남은 기능 Task는 dev-corp Ornith Spark field smoke뿐이다. 이 작업은 public IOP Edge와 mac-mini runner, 내부 Spark provider endpoint, runtime secret 파일을 함께 확인해야 하므로 작은 로컬 수정이 아니라 원격 검증 계획으로 분리한다. 목표는 `chat_template_kwargs.enable_thinking=false`가 direct Spark와 IOP 경유 요청 모두에서 provider-native think-off 동작으로 관측되는지 evidence를 남기는 것이다. - -## 사용자 리뷰 요청 흐름 - -사용자 리뷰 요청은 선택된 Milestone lock decision만 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 직접 사용자 프롬프트는 금지이며, code-review가 사용자 리뷰 요청의 정당성 검증과 실제 `USER_REVIEW.md` 작성을 소유한다. - -## Roadmap Targets - -- Milestone: `agent-roadmap/phase/routing-policy-model-orchestration/milestones/openai-compatible-provider-passthrough-contract-sync.md` -- Milestone link: [Milestone 문서](agent-roadmap/phase/routing-policy-model-orchestration/milestones/openai-compatible-provider-passthrough-contract-sync.md) -- Task ids: - - `devcorp-smoke`: dev-corp IOP 경유 Ornith Spark 요청에서 `chat_template_kwargs.enable_thinking=false`가 provider까지 전달된다. -- Completion mode: check-on-pass - -## 분석 결과 - -### 읽은 파일 - -- `agent-roadmap/current.md` -- `agent-roadmap/phase/routing-policy-model-orchestration/PHASE.md` -- `agent-roadmap/phase/routing-policy-model-orchestration/milestones/openai-compatible-provider-passthrough-contract-sync.md` -- `agent-roadmap/sdd/routing-policy-model-orchestration/openai-compatible-provider-passthrough-contract-sync/SDD.md` -- `agent-ops/rules/project/rules.md` -- `agent-ops/rules/common/rules-roadmap.md` -- `agent-ops/rules/project/domain/edge/rules.md` -- `agent-ops/rules/project/domain/node/rules.md` -- `agent-ops/rules/project/domain/platform-common/rules.md` -- `agent-ops/rules/project/domain/testing/rules.md` -- `agent-test/local/rules.md` -- `agent-test/local/edge-smoke.md` -- `agent-test/local/node-smoke.md` -- `agent-test/dev-corp/rules.md` -- `agent-test/dev-corp/edge-smoke.md` -- `agent-test/dev-corp/node-smoke.md` -- `agent-test/dev-corp/platform-common-smoke.md` -- `agent-test/dev-corp/inventory.yaml` -- `agent-ops/skills/project/dev-corp-runtime-deploy/SKILL.md` -- `agent-contract/index.md` -- `agent-contract/outer/openai-compatible-api.md` -- `agent-contract/inner/edge-node-runtime-wire.md` -- `apps/edge/internal/openai/chat_handler.go` -- `apps/edge/internal/openai/stream.go` -- `apps/edge/internal/openai/server_test.go` -- `apps/node/internal/adapters/openai_compat/openai_compat.go` -- `apps/node/internal/adapters/openai_compat/openai_compat_test.go` - -### SDD 기준 - -- SDD: `agent-roadmap/sdd/routing-policy-model-orchestration/openai-compatible-provider-passthrough-contract-sync/SDD.md` -- 상태: `[승인됨]`, SDD 잠금 해제, 사용자 리뷰 없음. -- Acceptance Scenario: `S10` -> Milestone Task `devcorp-smoke`. -- Evidence Map: `S10` requires dev-corp single-call smoke output summary with `devcorp-smoke`, first content latency, and reasoning/content observation. -- 구현 체크리스트는 S10 Evidence Map에서 역산해 provider direct preflight, IOP Edge route preflight, direct-vs-IOP think-off smoke summary를 만들도록 구성했다. - -### 테스트 환경 규칙 - -- `test_env`: `dev-corp`. -- `agent-test/dev-corp/rules.md`, `edge-smoke.md`, `node-smoke.md`, `platform-common-smoke.md`, `inventory.yaml`을 읽었다. -- 적용 명령: mac-mini runner `ssh fe@172.24.63.178`, repo root `/Users/fe/agent-work/iop-dev-corp`, public OpenAI base `https://digitalplatform.iop.ai.kr/v1`, Spark direct endpoints `http://192.168.2.2:8003/v1`, `http://192.168.2.4:8005/v1`. -- Secret rule: API key 원문은 출력하지 않는다. remote command는 `build/dev-corp-runtime/.secrets/openai_api_key`를 env로만 읽는다. - -#### 테스트 환경 프리플라이트 - -- runner: mac-mini `ssh fe@172.24.63.178`. -- repo root/workdir: `/Users/fe/agent-work/iop-dev-corp`. -- branch/HEAD/dirty/source sync: TEST-1에서 `git status --short --branch`와 `git log --oneline -1`로 기록한다. smoke-only 작업이므로 dirty가 있으면 결과에 남기고, 배포/rebuild가 필요하다고 판단될 때만 dev-corp deploy 절차로 전환한다. -- binary/artifact/config: Edge runtime은 public `iop.ai.kr`; config path는 inventory 기준 `/Users/toki/agent-work/iop-dev-corp/build/dev-corp-runtime/edge.yaml`. 이 plan은 config 수정이나 rebuild를 수행하지 않는다. -- command availability: TEST-1에서 `command -v python3`와 `command -v curl`을 확인한다. -- runtime identity: Edge id `dev-corp-edge`, Edge-Node TCP `iop.ai.kr:18087`. -- external hosts: mac-mini runner, Spark01 `192.168.2.2:8003`, Spark02 `192.168.2.4:8005`, public Edge `digitalplatform.iop.ai.kr`. -- blocker: SSH 불가, secret file unreadable, provider `/health` or `/v1/models` failure, public `/v1/models` auth failure, or Python/curl missing. - -### 테스트 커버리지 공백 - -- direct provider field smoke: existing Go tests cannot prove provider runtime behavior; this plan fills the dev-corp field evidence gap. -- IOP public Edge field smoke: local fake provider tests prove relay/body behavior, but not live Spark provider behavior; this plan fills S10. - -### 심볼 참조 - -- none. 이 plan은 symbol rename/remove를 수행하지 않는다. - -### 분할 판단 - -- split decision policy를 평가했다. -- 단일 plan 선택: 남은 범위는 `devcorp-smoke` 한 Task이고, preflight와 smoke가 같은 원격 환경에서 순차적으로 실행되어야 같은 evidence 묶음으로 리뷰할 수 있다. 코드/API와 call-site rollout 경계가 없고, 독립적으로 PASS 처리 가능한 선행 구현 subtask도 없다. - -### 범위 결정 근거 - -- 제외: runtime rebuild, config refresh, provider process restart, capacity 9/6 load smoke, `/v1/responses` provider-dependent smoke. 이번 plan은 `devcorp-smoke`의 single-call think-off evidence만 수집한다. -- 배포나 config drift가 발견되면 이 plan 안에서 임의 수정하지 않고 blocker 또는 후속 dev-corp deploy plan으로 기록한다. - -### 빌드 등급 - -- `cloud-G07`: 외부 runner, live provider, secret file, public Edge behavior가 중심이며 로컬 deterministic test만으로 완료할 수 없다. - -## 구현 체크리스트 - -- [ ] TEST-1 dev-corp runner, provider, public Edge preflight를 실행하고 blocker 여부를 `CODE_REVIEW-cloud-G07.md`에 기록한다. -- [ ] TEST-2 direct Spark와 IOP 경유 `chat_template_kwargs.enable_thinking=false` 단일 smoke를 실행하고 first content latency, reasoning delta 관측, content summary를 `CODE_REVIEW-cloud-G07.md`에 기록한다. -- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. - -### [TEST-1] Dev-Corp Preflight - -#### 문제 - -`devcorp-smoke`는 local checkout이 아니라 mac-mini runner와 public Edge, 내부 provider endpoint 상태에 의존한다. `agent-test/dev-corp/rules.md`는 runner checkout, provider `/health`와 `/v1/models`, public Edge auth를 먼저 확인하라고 요구한다. - -#### 해결 방법 - -아래 preflight를 실행하고 실패 지점을 정확히 기록한다. secret 값은 출력하지 않는다. - -```bash -ssh fe@172.24.63.178 'cd /Users/fe/agent-work/iop-dev-corp && \ - git status --short --branch && \ - git log --oneline -1 && \ - command -v python3 && \ - command -v curl && \ - test -r build/dev-corp-runtime/.secrets/openai_api_key' -``` - -```bash -ssh fe@172.24.63.178 'set -e -curl -fsS http://192.168.2.2:8003/health -curl -fsS http://192.168.2.2:8003/v1/models -curl -fsS http://192.168.2.4:8005/health -curl -fsS http://192.168.2.4:8005/v1/models -cd /Users/fe/agent-work/iop-dev-corp -OPENAI_API_KEY="$(cat build/dev-corp-runtime/.secrets/openai_api_key)" -curl -fsS -H "Authorization: Bearer ${OPENAI_API_KEY}" https://digitalplatform.iop.ai.kr/v1/models -' -``` - -#### 수정 파일 및 체크리스트 - -- [ ] repo source file 수정 없음. -- [ ] `CODE_REVIEW-cloud-G07.md`에 preflight stdout/stderr 또는 저장 경로를 기록한다. - -#### 테스트 작성 - -별도 테스트 파일 작성 없음. live environment preflight가 테스트다. - -#### 중간 검증 - -위 두 preflight command가 모두 exit 0이어야 TEST-2로 진행한다. - -### [TEST-2] Think-Off Field Smoke - -#### 문제 - -SDD S10은 direct Spark와 IOP 경유 Ornith Spark 요청에서 `chat_template_kwargs.enable_thinking=false`가 provider-native think-off 결과로 관측되어야 한다고 요구한다. local fake provider 테스트만으로는 실제 Spark/vLLM runtime behavior를 증명할 수 없다. - -#### 해결 방법 - -mac-mini runner에서 direct Spark01, direct Spark02, public IOP Edge에 같은 streaming Chat Completions 요청을 보내고 JSON summary를 남긴다. `reasoning_content`, `reasoning`, `reasoning_text` delta가 관측되면 fail로 기록한다. 첫 content latency는 초 단위로 기록한다. - -```bash -ssh fe@172.24.63.178 'cd /Users/fe/agent-work/iop-dev-corp && OPENAI_API_KEY="$(cat build/dev-corp-runtime/.secrets/openai_api_key)" python3 - <<'"'"'PY'"'"' -import json -import os -import time -import urllib.error -import urllib.request - -payload = { - "model": "ornith:35b", - "messages": [{"role": "user", "content": "Reply with one short sentence and do not include reasoning."}], - "stream": True, - "temperature": 0, - "max_tokens": 64, - "chat_template_kwargs": {"enable_thinking": False}, -} - -cases = [ - ("direct_spark01", "http://192.168.2.2:8003/v1/chat/completions", {}), - ("direct_spark02", "http://192.168.2.4:8005/v1/chat/completions", {}), - ("iop_public_ornith", "https://digitalplatform.iop.ai.kr/v1/chat/completions", {"Authorization": "Bearer " + os.environ["OPENAI_API_KEY"]}), -] - -def run_case(name, url, headers): - req_headers = {"Content-Type": "application/json"} - req_headers.update(headers) - req = urllib.request.Request(url, data=json.dumps(payload).encode("utf-8"), headers=req_headers, method="POST") - started = time.monotonic() - content = [] - reasoning_seen = False - first_content_sec = None - status = None - try: - with urllib.request.urlopen(req, timeout=180) as resp: - status = resp.status - for raw in resp: - line = raw.decode("utf-8", "replace").strip() - if not line.startswith("data: "): - continue - data = line[6:] - if data == "[DONE]": - break - obj = json.loads(data) - for choice in obj.get("choices", []): - delta = choice.get("delta") or {} - if delta.get("reasoning_content") or delta.get("reasoning") or delta.get("reasoning_text"): - reasoning_seen = True - piece = delta.get("content") - if piece: - if first_content_sec is None: - first_content_sec = round(time.monotonic() - started, 3) - content.append(piece) - except urllib.error.HTTPError as exc: - body = exc.read().decode("utf-8", "replace") - return {"name": name, "ok": False, "status": exc.code, "error_body": body[:500]} - except Exception as exc: - return {"name": name, "ok": False, "error": repr(exc)} - text = "".join(content).strip() - return { - "name": name, - "ok": status == 200 and first_content_sec is not None and not reasoning_seen, - "status": status, - "first_content_sec": first_content_sec, - "reasoning_seen": reasoning_seen, - "content_preview": text[:200], - } - -results = [run_case(*case) for case in cases] -print(json.dumps({"payload": {"model": payload["model"], "chat_template_kwargs": payload["chat_template_kwargs"]}, "results": results}, ensure_ascii=False, indent=2)) -if not all(item.get("ok") for item in results): - raise SystemExit(1) -PY' -``` - -#### 수정 파일 및 체크리스트 - -- [ ] repo source file 수정 없음. -- [ ] direct Spark01/Spark02 결과와 IOP public 결과를 `CODE_REVIEW-cloud-G07.md`에 기록한다. -- [ ] 실패 시 HTTP status/body, first content latency, reasoning 관측 여부를 분리 기록한다. - -#### 테스트 작성 - -별도 unit test 작성 없음. SDD S10은 live dev-corp field smoke evidence가 테스트다. - -#### 중간 검증 - -Python smoke command가 exit 0이어야 한다. 출력 JSON에서 세 case 모두 `ok: true`, `reasoning_seen: false`, `first_content_sec` 값이 있어야 한다. - -## 수정 파일 요약 - -| 파일 | 항목 | -|------|------| -| 없음 | TEST-1, TEST-2는 검증/evidence 수집 작업이다. | -| `agent-task/m-openai-compatible-provider-passthrough-contract-sync/CODE_REVIEW-cloud-G07.md` | 구현 에이전트가 실제 검증 출력과 판단을 기록한다. | - -## 최종 검증 - -최종 검증은 TEST-1 preflight 두 command와 TEST-2 Python smoke command를 순서대로 실행하는 것이다. 모든 command가 exit 0이어야 하고, TEST-2 JSON summary의 세 case가 모두 `ok: true`여야 한다. - -모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.