<!-- task=m-ollama-field-validation-follow-up/01_split_host_smoke plan=0 tag=TEST -->

# Plan - TEST

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

구현 완료 전 `CODE_REVIEW-cloud-G07.md`의 구현 에이전트 소유 섹션을 반드시 채운다.
검증 명령은 실제로 실행하고 stdout/stderr를 기록하며, active 파일은 그대로 둔 채 리뷰 준비 상태를 보고한다.
최종 archive, `complete.log`, 코드리뷰 판정은 code-review 스킬 전용이다.

## 배경

현재 Milestone은 code-server 컨테이너를 Edge로, 컨테이너 호스트를 Node/Ollama host로 두는 split-host field smoke를 요구한다.
fake Ollama 보조 smoke는 이미 있으나, 실제 Ollama와 컨테이너-호스트 네트워크 경계를 통과하는 검증은 별도로 증거를 남겨야 한다.
작은 preflight에서 컨테이너 IP는 `192.168.16.14`, host Ollama 후보 `host.docker.internal:11434`와 `192.168.0.91:11434` 응답은 확인했다.

## 분석 결과

### 읽은 파일

- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-ops/skills/common/router.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-ops/skills/project/e2e-smoke/SKILL.md`
- `agent-ops/roadmap/current.md`
- `agent-ops/roadmap/ROADMAP.md`
- `agent-ops/roadmap/phase/serving-routing-optimization/PHASE.md`
- `agent-ops/roadmap/phase/serving-routing-optimization/milestones/ollama-field-validation-follow-up.md`
- `configs/edge.yaml`
- `configs/node.yaml`
- `bin/edge.sh`
- `bin/node.sh`
- `docs/deploy-dev.md`
- `scripts/e2e-openai-ollama.sh`
- `scripts/e2e-smoke.sh`
- `Makefile`

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

- `scripts/e2e-openai-ollama.sh`는 fake Ollama와 같은 컨테이너 안의 Edge/Node를 검증하므로 실제 host Ollama와 split-host 네트워크 경계를 덮지 않는다.
- `scripts/e2e-smoke.sh`는 CLI/mock 사용자 흐름을 검증하지만 Ollama OpenAI-compatible 실제 모델 호출을 덮지 않는다.
- 이번 작업의 핵심 검증은 host Node registration, `/v1/models`, non-streaming chat, streaming SSE와 `data: [DONE]` 실제 출력이다.

### 심볼 참조

- none. 심볼 rename/remove가 없는 검증 작업이다.

### 분할 판단

- split decision policy를 먼저 평가했다.
- 공유 task group은 `m-ollama-field-validation-follow-up`이고, 이번 active subtask는 `01_split_host_smoke`다.
- 이번 subtask는 preflight 이후 실제 split-host smoke 실행과 증거 기록만 담당한다. issue triage와 follow-up scope 정리는 smoke 결과를 본 뒤 같은 Milestone task group의 sibling subtask로 분리할 수 있다.

### 범위 결정 근거

- 기본 `configs/*.yaml`은 필드 테스트 값으로 덮어쓰지 않는다. `docs/deploy-dev.md:137`도 임시 config 사용을 요구한다.
- Jenkins, bootstrap artifact 다운로드, Control Plane registry, OTO bootstrap은 이번 Milestone Task의 직접 검증 범위를 넘으므로 제외한다.
- 코드 수정은 하지 않고, `CODE_REVIEW-cloud-G07.md`에 실제 실행 증거만 채운다. smoke 중 결함이 발견되면 같은 Milestone task group의 후속 fix plan으로 분리한다.

### 빌드 등급

- `cloud-G07`: long-running Edge/Node process orchestration, host/container network, 실제 Ollama 외부 도구가 성공 조건이라 terminal-agent work에 해당한다.

## 구현 체크리스트

- [ ] [TEST-1] split-host 임시 Edge/Node config를 준비하고 기본 `configs/*.yaml`을 오염시키지 않는다.
- [ ] [TEST-2] 컨테이너 Edge와 host Node를 실행해 host Node가 컨테이너 Edge 주소로 등록되는지 확인한다. 검증: Edge 출력에서 node registration을 확인한다.
- [ ] [TEST-3] split-host 환경에서 실제 Ollama `/v1/models`, non-streaming chat, streaming chat 결과를 기록한다. 검증: 실제 모델 조회와 non-streaming/streaming chat 결과 또는 실패 로그가 남아 있다.
- [ ] [TEST-4] 결과를 보고 후속 작업 후보를 API compatibility, option passthrough, reasoning stream, config/docs, routing/fallback 중 하나 이상으로 분류할지 판단한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [TEST-1] Split-Host Config Preparation

#### 문제

현재 기본 edge config는 `configs/edge.yaml:28`부터 OpenAI-compatible Ollama 설정을 갖지만, 필드 테스트 값으로 기본 파일을 덮어쓰면 안 된다.
`docs/deploy-dev.md:137`부터 임시 config 사용을 명시하고, `bin/edge.sh:6`과 `bin/node.sh:6`은 각각 `IOP_EDGE_CONFIG`, `IOP_NODE_CONFIG` override를 지원한다.

#### 해결 방법

임시 파일만 사용한다. 컨테이너에서 `/tmp/iop-edge-split-host.yaml`, host에서 `/tmp/iop-node-split-host.yaml`을 만든다.

Container Edge config:

```yaml
edge:
  id: "edge-code-server-split-host"
  name: "Code Server Split Host Edge"

server:
  listen: "0.0.0.0:9090"

logging:
  level: "info"
  pretty: true

metrics:
  port: 9092

openai:
  enabled: true
  listen: "0.0.0.0:8080"
  node: "node-container-host-ollama"
  adapter: "ollama"
  target: ""
  models:
    - "gemma4:26b"
  session_id: "openai-split-host"
  timeout_sec: 300

console:
  adapter: "cli"
  target: "antigravity"
  session_id: "default"
  timeout_sec: 300

nodes:
  - id: "node-container-host-ollama"
    alias: "container-host-ollama"
    token: "split-host-token"
    adapters:
      ollama:
        enabled: true
        base_url: "http://192.168.0.91:11434"
        context_size: 262144
      cli:
        enabled: false
```

Host Node config:

```yaml
transport:
  edge_addr: "192.168.16.14:9090"
  token: "split-host-token"

logging:
  level: "info"
  pretty: true

metrics:
  port: 9091
```

If the host cannot reach `192.168.16.14:9090`, record the failing command output and switch only the temporary node config to the reachable host-published Edge address.
If host Ollama cannot reach `http://192.168.0.91:11434` from the host, record the failure and switch only the temporary edge config `base_url` to `http://127.0.0.1:11434`.

#### 수정 파일 및 체크리스트

- [ ] No repo source files are modified for this item.
- [ ] `/tmp/iop-edge-split-host.yaml` is created in the container.
- [ ] `/tmp/iop-node-split-host.yaml` is created on the host or the host-reachable node runtime environment.

#### 테스트 작성

테스트 파일은 추가하지 않는다. 이번 항목은 repo 변경 없이 실제 환경용 임시 config를 만드는 작업이다.

#### 중간 검증

```bash
curl -fsS --max-time 2 http://192.168.0.91:11434/api/tags
```

Expected: JSON containing `gemma4:26b`.

### [TEST-2] Edge/Node Registration

#### 문제

Milestone Task `split-host-env`는 `ollama-field-validation-follow-up.md:41`에서 host Node가 컨테이너 Edge 주소로 등록되는 것을 완료 조건으로 둔다.
`bin/node.sh:25`부터 Edge reachability를 먼저 확인하고, 실패 시 명확히 종료한다.

#### 해결 방법

세션을 분리해 실행한다.

Container shell A:

```bash
IOP_EDGE_CONFIG=/tmp/iop-edge-split-host.yaml ./bin/edge.sh
```

Host shell B:

```bash
cd /config/workspace/iop
IOP_NODE_CONFIG=/tmp/iop-node-split-host.yaml ./bin/node.sh
```

If the host does not have the repo path mounted, build/copy the field binary and run:

```bash
./iop-node serve --config /tmp/iop-node-split-host.yaml
```

#### 수정 파일 및 체크리스트

- [ ] No repo source files are modified for this item.
- [ ] Edge process output is captured or copied into `CODE_REVIEW-cloud-G07.md`.
- [ ] Node process output is captured or copied into `CODE_REVIEW-cloud-G07.md`.
- [ ] Edge output shows node registration.

#### 테스트 작성

테스트 파일은 추가하지 않는다. 실제 host/container split process 실행 자체가 검증이다.

#### 중간 검증

```bash
timeout 1 bash -c 'cat < /dev/null > /dev/tcp/192.168.16.14/9090'
```

Expected from host: exit code 0 before starting Node.

### [TEST-3] Real Ollama OpenAI-Compatible Smoke

#### 문제

Milestone Task `field-test`는 `ollama-field-validation-follow-up.md:42`에서 실제 모델 조회와 non-streaming/streaming chat 결과 또는 실패 로그를 요구한다.
`scripts/e2e-openai-ollama.sh:25`부터 fake Ollama와 random local ports를 쓰므로 이 실제 split-host 검증을 대체하지 않는다.

#### 해결 방법

Container shell C에서 Edge OpenAI-compatible API를 호출하고 출력 파일을 남긴다.

```bash
curl -fsS http://127.0.0.1:8080/v1/models | tee /tmp/iop-split-host-models.json
```

```bash
curl -fsS http://127.0.0.1:8080/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{"model":"gemma4:26b","messages":[{"role":"user","content":"Reply with IOP_OLLAMA_E2E_OK only."}]}' \
  | tee /tmp/iop-split-host-chat.json
```

```bash
curl -fsS -N http://127.0.0.1:8080/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{"model":"gemma4:26b","stream":true,"messages":[{"role":"user","content":"Reply with IOP_OLLAMA_STREAM_OK only."}]}' \
  | tee /tmp/iop-split-host-stream.txt
```

#### 수정 파일 및 체크리스트

- [ ] No repo source files are modified for this item.
- [ ] `/tmp/iop-split-host-models.json` contains `gemma4:26b`.
- [ ] `/tmp/iop-split-host-chat.json` contains a non-empty assistant response or a captured failure body.
- [ ] `/tmp/iop-split-host-stream.txt` contains SSE `data:` chunks and `data: [DONE]`, or a captured failure body.
- [ ] Reasoning/content separation is noted if the model emits reasoning.

#### 테스트 작성

테스트 파일은 추가하지 않는다. This is an external field smoke using real Ollama.

#### 중간 검증

```bash
grep -F 'data: [DONE]' /tmp/iop-split-host-stream.txt
```

Expected: a matching line if streaming succeeds.

### [TEST-4] Result Classification

#### 문제

Milestone Task `issue-triage` and `follow-up-scope` at `ollama-field-validation-follow-up.md:43` and `ollama-field-validation-follow-up.md:44` require follow-up work to be classified after the field test.

#### 해결 방법

Classify the final result in `CODE_REVIEW-cloud-G07.md`:

- PASS without follow-up: record no code fix needed.
- Config/docs issue: temporary address, port, or host path adjustments only.
- API compatibility: `/v1/models` or chat schema mismatch.
- Reasoning stream: thinking/reasoning chunks mixed with content or missing.
- Option passthrough: context/options not respected.
- Routing/fallback: node selection, unavailable node, timeout, or fallback behavior.

#### 수정 파일 및 체크리스트

- [ ] `CODE_REVIEW-cloud-G07.md` has actual result classification.
- [ ] Any code/doc fix discovered is left for a new plan under this Milestone task group, not silently patched in this task.

#### 테스트 작성

테스트 파일은 추가하지 않는다. 이 항목은 smoke 결과 분류만 담당한다.

#### 중간 검증

```bash
test -s /tmp/iop-split-host-models.json -a -s /tmp/iop-split-host-chat.json -a -s /tmp/iop-split-host-stream.txt
```

Expected: exit code 0 if all smoke outputs were captured.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `agent-task/m-ollama-field-validation-follow-up/01_split_host_smoke/CODE_REVIEW-cloud-G07.md` | TEST-1, TEST-2, TEST-3, TEST-4 |
| `/tmp/iop-edge-split-host.yaml` | TEST-1 temporary runtime artifact |
| `/tmp/iop-node-split-host.yaml` | TEST-1 temporary runtime artifact |
| `/tmp/iop-split-host-models.json` | TEST-3 temporary evidence |
| `/tmp/iop-split-host-chat.json` | TEST-3 temporary evidence |
| `/tmp/iop-split-host-stream.txt` | TEST-3 temporary evidence |

## 최종 검증

```bash
go test -count=1 ./...
```

Expected: all Go packages pass without using cache.

```bash
git diff --check
```

Expected: no whitespace errors.

```bash
grep -F 'gemma4:26b' /tmp/iop-split-host-models.json
```

Expected: model entry is present.

```bash
grep -F 'IOP_OLLAMA' /tmp/iop-split-host-chat.json
```

Expected: the non-streaming response includes the requested token, unless the recorded failure classification explains the absence.

```bash
grep -F 'data: [DONE]' /tmp/iop-split-host-stream.txt
```

Expected: streaming response terminates with `[DONE]`, unless the recorded failure classification explains the absence.

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