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

# Code Review Reference - REVIEW_UX

> **[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, 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`.
> 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-05-28
task=m-edge-local-dev-config-runtime/03+01,02_help_smoke_bundle_docs, plan=1, tag=REVIEW_UX

## 이 파일을 읽는 리뷰 에이전트에게

> **[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-edge-local-dev-config-runtime/03+01,02_help_smoke_bundle_docs/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다.
4. PASS이고 task group이 `m-edge-local-dev-config-runtime`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다.
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.

---

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [REVIEW_UX-1] Empty Content Smoke Failure | [x] |
| [REVIEW_UX-2] Bundle-Local Docs Source Of Truth | [x] |

## 구현 체크리스트

- [x] [REVIEW_UX-1] `smoke openai` rejects empty chat completion content and has an `httptest.Server` regression test for empty content.
- [x] [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.
- [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] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [x] PASS이면 active task 디렉터리 `agent-task/m-edge-local-dev-config-runtime/03+01,02_help_smoke_bundle_docs/`를 `agent-task/archive/YYYY/MM/m-edge-local-dev-config-runtime/03+01,02_help_smoke_bundle_docs/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [x] PASS이고 task group이 `m-edge-local-dev-config-runtime`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [x] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-edge-local-dev-config-runtime/`를 제거하거나, 남은 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가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다.

## 계획 대비 변경 사항

- 없음. 계획된 Required 변경이 완벽하게 반영되었습니다.

## 주요 설계 결정

- `smoke openai`에서 `choices[0].message.content`가 비어 있거나 공백인 경우, 단순 출력이 아닌 `[FAILED]` 메시지와 함께 의미 있는 `POST ... returned empty assistant message content` 에러를 리턴하도록 처리했습니다.
- 유닛 테스트 `TestSmokeOpenAICommandFailure` 내에 `httptest.NewServer`를 활용해 공백 문자("   ") 응답 시 실제로 실패 처리와 적절한 메시지가 반환되는지를 확인하는 3번째 회귀 서브 테스트 케이스를 구현하여 안정성을 보장했습니다.
- `docs/deploy-dev.md` 내에 기준 경로가 아닌 repo 예시(`configs/edge.yaml`)에 지나치게 의존적이던 설명을 제거하고, `config init`을 통한 bundle-local `edge.yaml`이 유일한 primary source of truth임을 명확하게 재기술했습니다.

## 사용자 리뷰 요청

- 상태: 없음
- 사유 유형: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 재개 조건: 없음

## 리뷰어를 위한 체크포인트

- `smoke openai` must fail on empty chat completion content and preserve actionable stdout/error output.
- The new regression test must use `httptest.Server`, not a real OpenAI/Ollama endpoint.
- `docs/deploy-dev.md` must make bundle-local `edge.yaml` the default dev source of truth, with repo config only as a clearly labeled fallback/example if mentioned.
- Final docs `rg` must include the stale `configs/edge.yaml` default-language pattern that the prior review missed.

## 검증 결과

필수 규칙:
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다.

### REVIEW_UX-1 중간 검증
```bash
$ go test -count=1 ./apps/edge/cmd/edge
ok      iop/apps/edge/cmd/edge  0.021s
```

### REVIEW_UX-2 중간 검증
```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
docs/deploy-dev.md
104:dev 초기 단계에서는 `iop-edge config init` 명령을 실행해 생성한 bundle-local `edge.yaml`을 기준으로 포트와 node token을 맞추며, 이 bundle-local `edge.yaml`이 개발/테스트의 primary source of truth다. repo의 `configs/edge.yaml`은 직접 편집하거나 참조하는 공식 기본 경로가 아니며 단지 로컬 개발 환경용 fallback 또는 예시 템플릿일 뿐이다. 이후 Control Plane enrollment/config sync가 붙으면 edge 설정 파일은 bootstrap 정보만 남기는 방향으로 줄인다.
554:`setup`의 `--config` 기본값은 `/etc/iop/edge.yaml`이다. dev 단계 명령(`serve`, `console`, `config init/print/check`, `env`, `node register`)의 root persistent `--config` 기본값은 bundle-local `edge.yaml` (없을 시 executable-adjacent `edge.yaml`, 그 다음 repo dev fallback `configs/edge.yaml`)로 확인된다.
apps/edge/README.md
106:`setup`의 `--config` 기본값은 `/etc/iop/edge.yaml`이다. dev 단계 명령(`serve`, `console`, `config init/print/check`, `env`, `node register`)의 root persistent `--config` 기본값은 bundle-local `edge.yaml` (없을 시 executable-adjacent `edge.yaml`, 그 다음 repo dev fallback `configs/edge.yaml`)로 확인된다. 기존 설정 파일은 그대로 두며 `--overwrite-config`를 지정해야 덮어쓴다.
```

### 최종 검증
```bash
$ go test -count=1 ./apps/edge/cmd/edge ./packages/config
ok      iop/apps/edge/cmd/edge  0.022s
ok      iop/packages/config     0.009s
```

```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)
wrote edge.yaml
config: edge.yaml
binary_dir: /tmp/tmp.RiekFTKkpC
log_path: /tmp/tmp.RiekFTKkpC/logs/edge.log
advertise_host: 192.168.16.14 (auto)
node_transport: 192.168.16.14:9090
artifact_base_url: (unset)
openai_url: (disabled)
a2a_url: (disabled)
configured_nodes: 0 (none)
connected_nodes: offline (env does not start edge)
Registered node node-silicon-ollama
curl -fsSL http://toki-labs.com:18080/bootstrap/node-darwin-arm64.sh | bash -s b6d28ce7-3d61-435a-96f4-eb9db32380d6
Config Source: edge.yaml

NODE ID                   ALIAS                TOKEN                                    STATUS
----------------------------------------------------------------------------------------------------
node-silicon-ollama       node-silicon-ollama  b6d28ce7-3d61-435a-96f4-eb9db32380d6     offline (configured)
OK edge.yaml
IOP Edge is the backend execution group controller that coordinates multiple execution nodes.
It manages token-based node registration, node registry, node configuration delivery, and OpenAI-compatible routing.

The official local development and field test flow is as follows:
  1. config init    - Write a clean local edge.yaml configuration template.
  2. env            - Verify resolved local environment, log paths, and listen URLs.
  3. node register  - Add a node record to edge.yaml and generate its one-line bootstrap command.
  4. config check   - Validate the edge.yaml configuration structure and constraints.
  5. serve (or console) - Start the Edge server (or interactive console) to accept connections.
  6. nodes list     - List configured nodes and show their connection status.
  7. smoke openai   - Perform an E2E health and chat completion smoke test on the OpenAI endpoint.
Usage:
  edge [command]

Examples:
  iop-edge config init
  iop-edge env
  iop-edge node register my-node --adapter cli
  iop-edge config check
  iop-edge serve
  iop-edge nodes list
  iop-edge smoke openai --model gemma4:26b

Available Commands:
  completion  Generate the autocompletion script for the specified shell
  config      Create, inspect, and validate edge config
  console     Start the edge server with an interactive node console
  env         Print resolved edge environment without starting edge
  help        Help about any command
  node        Manage edge nodes
  nodes       Inspect registered edge nodes
  serve       Start the edge server
  setup       Prepare host environment and systemd unit for iop-edge
  smoke       Run verification and smoke tests
  version     Print edge binary version

Flags:
  -c, --config string   config file path (defaults to bundle-local edge.yaml)
  -h, --help            help for edge

Use "edge [command] --help" for more information about a command.
Run diagnostic smoke tests to verify the connectivity and functionality of IOP Edge components.
Usage:
  edge smoke [command]

Available Commands:
  openai      Perform OpenAI-compatible endpoint smoke test

Flags:
  -h, --help   help for smoke

Global Flags:
  -c, --config string   config file path (defaults to bundle-local edge.yaml)

Use "edge smoke [command] --help" for more information about a command.
```

```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
docs/field-bootstrap-user-test.md
10:- `iop-node serve --config ...` 직접 실행
docs/deploy-dev.md
108:사용자/field 기본 경로에서 Node는 직접 설정 파일을 만들거나 `iop-node serve --config ...`로 실행하지 않습니다.
apps/edge/README.md
31:field 사용자 기본 경로는 `iop-edge`가 제시한 bootstrap 명령으로 Node를 연결하는 흐름이다. `bin/edge.sh`와 `bin/node.sh`는 repo 내부 개발 진단 helper이며 공식 사용자 UX로 안내하지 않는다.
```

---

> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
> If anything is blank, go back and fill it in before saving this file.
> Leave review-agent-only sections unchanged.

Sections and their ownership:

| 섹션 | 소유자 | 설명 |
|------|--------|------|
| 헤더 주석, 개요(date/task/plan/tag), 리뷰 에이전트 지시 | 스텁 생성 시 고정 | 구현 에이전트가 수정하거나 실행하지 않음 |
| 구현 항목별 완료 여부 (항목명) | 스텁 생성 시 고정 | `[ ]` -> `[x]` 체크만 구현 에이전트가 수행 |
| 구현 체크리스트 (항목 텍스트/순서) | follow-up plan에서 복사해 스텁 생성 시 고정 | 구현 에이전트가 `[ ]` -> `[x]` 체크만 수행; 마지막 체크박스는 저장 전 필수 |
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify or check this section |
| 계획 대비 변경 사항, 주요 설계 결정 | 구현 에이전트가 채움 | placeholder 텍스트를 실제 내용으로 교체 |
| 사용자 리뷰 요청 | 구현 에이전트가 채움 | 진행에 사용자 입력이 필요하지 않으면 `상태: 없음` 유지 |
| 리뷰어를 위한 체크포인트 | 스텁 생성 시 고정 | 계획에서 추출한 리뷰 포인트 |
| 검증 결과 (섹션 제목 + 명령) | 스텁 생성 시 고정 | 실행 출력만 구현 에이전트가 채움 |
| 코드리뷰 결과 | 리뷰 에이전트가 append | 스텁에 포함하지 않음 |

## 코드리뷰 결과

- 종합 판정: PASS

### 차원별 평가

| 차원 | 평가 | 근거 |
|---|---|---|
| Correctness | Pass | `smoke openai`가 빈 assistant content를 실패 처리한다. |
| Completeness | Pass | 후속 Required 2건과 구현 체크리스트가 모두 완료되었다. |
| Test coverage | Pass | `httptest.Server` 기반 empty content 회귀 테스트가 추가되었다. |
| API contract | Pass | CLI stdout/error 계약과 docs의 bundle-local source-of-truth 설명이 계획 범위와 일치한다. |
| Code quality | Pass | 후속 변경 범위에서 debug print, dead code, 불필요한 범위 확장은 발견하지 못했다. |
| Plan deviation | Pass | 이전 리뷰의 Required 항목만 좁게 수정했다. |
| Verification trust | Pass | `go test`, docs `rg`, clean bundle 명령을 재실행해 기록과 일치함을 확인했다. |

### 발견된 문제

- 없음

### 다음 단계

- PASS: `complete.log` 작성 후 task 디렉터리를 archive로 이동한다.
