<!-- task=m-edge-local-dev-config-runtime/03+01,02_help_smoke_bundle_docs plan=1 tag=REVIEW_UX -->

# Plan - REVIEW_UX

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

이 계획은 이전 `UX` 리뷰의 FAIL Required 2건만 해결한다. 범위를 넓히지 말고, 구현 후 `CODE_REVIEW-cloud-G07.md`의 구현 에이전트 소유 섹션에 실제 변경 내용과 검증 출력을 채운 뒤 active 파일을 그대로 두고 리뷰 준비를 보고한다. finalization은 code-review-skill 전용이다.

## 배경

1차 구현은 `nodes list`, `smoke openai`, help, 문서 일부를 추가했지만 리뷰에서 두 가지 Required가 남았다.

- `smoke openai`가 빈 chat completion content를 성공으로 처리할 수 있다.
- `docs/deploy-dev.md`가 아직 repo `configs/edge.yaml`을 dev 기준 source of truth처럼 안내한다.

## 분석 결과

### 읽은 파일

- `agent-task/m-edge-local-dev-config-runtime/03+01,02_help_smoke_bundle_docs/code_review_cloud_G07_0.log`
- `agent-task/m-edge-local-dev-config-runtime/03+01,02_help_smoke_bundle_docs/plan_cloud_G07_0.log`
- `apps/edge/cmd/edge/main.go`
- `apps/edge/cmd/edge/main_test.go`
- `docs/deploy-dev.md`
- `apps/edge/README.md`
- `docs/field-bootstrap-user-test.md`

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

- `smoke openai` failure test가 non-200 응답만 다루고, 빈 assistant content 응답을 실패로 검증하지 않는다.
- 문서 검증 `rg`가 plan에서 금지한 repo `configs/edge.yaml` 기본 경로 잔존 여부를 직접 확인하지 않았다.

### 범위 결정 근거

새 명령 추가, bootstrap flow 재설계, OpenAI 서버 내부 변경, full field E2E 추가는 이번 후속 범위가 아니다. 기존 `smoke openai` CLI의 실패 판정과 docs 문구만 좁게 수정한다.

### 빌드 등급

build=`cloud-G07`, review=`cloud-G07`. CLI smoke 동작, stdout/stderr 계약, docs 검증 신뢰도 회복 작업이다.

## 구현 체크리스트

- [ ] [REVIEW_UX-1] `smoke openai` rejects empty chat completion content and has an `httptest.Server` regression test for empty content.
- [ ] [REVIEW_UX-2] `docs/deploy-dev.md` no longer presents repo `configs/edge.yaml` as the dev source of truth and clearly states bundle-local `edge.yaml`/`config init` as the default.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

기존 task directory dependency를 유지한다. 추가 hidden dependency를 만들지 않는다.

### [REVIEW_UX-1] Empty Content Smoke Failure

#### 문제

[apps/edge/cmd/edge/main.go](/config/workspace/iop/apps/edge/cmd/edge/main.go:1046)는 `choices[0].message.content`가 빈 문자열이어도 성공 메시지를 출력한다. 이전 계획은 empty content failure test를 요구했지만 [apps/edge/cmd/edge/main_test.go](/config/workspace/iop/apps/edge/cmd/edge/main_test.go:1023)는 non-200 실패만 검증한다.

Before:

```go
content := chatResp.Choices[0].Message.Content
fmt.Fprintf(cmd.OutOrStdout(), "[OK]\n")
fmt.Fprintf(cmd.OutOrStdout(), "Response Content: %q\n\n", content)
fmt.Fprintf(cmd.OutOrStdout(), "IOP Edge OpenAI Smoke Test SUCCESS!\n")
```

#### 해결 방법

`strings.TrimSpace(content) == ""`이면 `[FAILED]`를 출력하고 actionable error를 반환한다. 같은 `httptest.Server` 방식으로 `/healthz`, `/v1/models`는 성공하지만 `/v1/chat/completions`가 빈 content를 반환하는 regression test를 추가한다.

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

- [ ] `apps/edge/cmd/edge/main.go`: empty content 실패 처리 추가.
- [ ] `apps/edge/cmd/edge/main_test.go`: empty content 실패 테스트 추가.

#### 테스트 작성

`TestSmokeOpenAICommandFailure`에 subcase를 추가하거나 새 테스트를 만든다. 에러 메시지는 빈 content와 chat endpoint를 식별할 수 있어야 한다.

#### 중간 검증

```bash
go test -count=1 ./apps/edge/cmd/edge
```

Expected: pass; cache output is not acceptable.

### [REVIEW_UX-2] Bundle-Local Docs Source Of Truth

#### 문제

[docs/deploy-dev.md](/config/workspace/iop/docs/deploy-dev.md:104)가 “dev 초기 단계에서는 repo의 `configs/edge.yaml`을 기준으로 포트와 node token을 맞춘다”고 안내한다. 이 Milestone의 기준은 bundle-local `edge.yaml`이고, repo `configs/edge.yaml`은 fallback/개발 예시일 뿐이다.

Before:

```text
dev 초기 단계에서는 repo의 `configs/edge.yaml`을 기준으로 포트와 node token을 맞춘다.
```

#### 해결 방법

해당 문구를 `iop-edge config init`이 생성하는 bundle-local `edge.yaml` 기준으로 바꾼다. repo `configs/edge.yaml` 언급이 남아야 한다면 default/user path가 아니라 repo 개발 fallback 또는 예시임을 분명히 한다.

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

- [ ] `docs/deploy-dev.md`: Edge 실행 섹션의 source-of-truth 문구 수정.
- [ ] 필요 시 `apps/edge/README.md`의 repo fallback 설명이 default 사용자 경로로 읽히지 않는지 확인한다.

#### 테스트 작성

문서 전용 회귀는 deterministic `rg --sort path` 검증으로 충분하다.

#### 중간 검증

```bash
rg --sort path 'dev 초기 단계에서는 repo의|repo의 `configs/edge\.yaml`|repo dev fallback|bundle-local `edge\.yaml`' docs/deploy-dev.md apps/edge/README.md docs/field-bootstrap-user-test.md
```

Expected: `docs/deploy-dev.md`에 repo `configs/edge.yaml`을 dev source of truth로 말하는 문구가 없어야 한다. repo fallback 문구가 남으면 fallback임이 명확해야 한다.

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `apps/edge/cmd/edge/main.go` | REVIEW_UX-1 |
| `apps/edge/cmd/edge/main_test.go` | REVIEW_UX-1 |
| `docs/deploy-dev.md` | REVIEW_UX-2 |
| `apps/edge/README.md` | REVIEW_UX-2 확인만, 필요 시 최소 수정 |

## 최종 검증

```bash
go test -count=1 ./apps/edge/cmd/edge ./packages/config
```

```bash
tmp="$(mktemp -d)" && go build -o "$tmp/iop-edge" ./apps/edge/cmd/edge && go build -o "$tmp/iop-node" ./apps/node/cmd/node && (cd "$tmp" && ./iop-edge config init --force && ./iop-edge env && ./iop-edge node register node-silicon-ollama --adapter ollama --ollama-base-url http://127.0.0.1:11434 --target darwin-arm64 --artifact-base-url http://toki-labs.com:18080 && ./iop-edge nodes list && ./iop-edge config check && ./iop-edge help && ./iop-edge smoke --help)
```

```bash
rg --sort path 'dev 초기 단계에서는 repo의|repo의 `configs/edge\.yaml`|bin/edge.sh|bin/node.sh|dev deploy binary|iop-node serve --config' docs/field-bootstrap-user-test.md docs/deploy-dev.md apps/edge/README.md
```

Expected: tests pass; clean bundle command completes deterministic non-network commands; docs search shows no default-user-path references except explicit negative warnings or clearly labeled repo-internal diagnostics.

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