<!-- task=m-edge-local-dev-config-runtime/01_config_runtime_env plan=1 tag=REVIEW_CONFIG -->

# Plan - REVIEW_CONFIG

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

`CODE_REVIEW-cloud-G07.md`의 구현 에이전트 소유 섹션 작성은 필수다. 구현 후 검증을 실행하고 실제 출력과 구현 메모를 채운 뒤 active 파일을 그대로 두고 리뷰 준비를 보고한다. finalization은 code-review-skill 전용이다.

## 배경

이전 리뷰는 bundle-local log default 자체는 통과했지만, setup이 생성하는 systemd/service config와 새 log default가 충돌할 수 있음을 발견했다. `iop-edge setup` 경로는 `User=iop` 서비스로 실행되므로 빈 `logging.path`가 `/usr/local/bin/logs/edge.log`로 해석되면 기본 설치가 시작 실패할 수 있다. 후속 작업은 setup용 config template과 bundle `config init` template 책임을 분리하는 좁은 수정이다.

## 분석 결과

### 읽은 파일

- `agent-task/m-edge-local-dev-config-runtime/01_config_runtime_env/plan_cloud_G07_0.log`
- `agent-task/m-edge-local-dev-config-runtime/01_config_runtime_env/code_review_cloud_G07_0.log`
- `apps/edge/cmd/edge/main.go`
- `apps/edge/cmd/edge/main_test.go`
- `apps/edge/internal/bootstrap/runtime.go`
- `apps/edge/internal/bootstrap/runtime_test.go`
- `apps/edge/internal/opsconsole/console.go`
- `packages/config/config.go`
- `packages/config/config_test.go`
- `packages/hostsetup/templates.go`
- `packages/hostsetup/setup.go`
- `packages/hostsetup/systemd.go`
- `packages/hostsetup/setup_test.go`
- `packages/observability/observability.go`
- `packages/observability/observability_test.go`
- `configs/edge.yaml`

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

- Bundle `config init` smoke는 통과하지만, 출력 template이 새 config keys와 빈 bundle log path를 명시하는지 검증하지 않는다.
- `setup --dry-run`은 `/etc/iop/edge.yaml` 기본은 검증하지만, config preview가 service-owned log path를 제공하는지 검증하지 않는다.
- Hostsetup template 단위 테스트가 없어 setup config와 bundle config의 log-path 차이를 고정하지 못한다.

### 심볼 참조

- none. Rename/remove 계획은 없다.

### 분할 판단

Split decision policy를 평가했다. 문제는 `hostsetup` template과 `config init` 호출부 사이의 단일 책임 분리이며, 수정 파일 수는 몇 개지만 검증 목표가 하나라 같은 active subtask 안의 단일 follow-up plan이 더 안전하다. 기존 sibling subtask 의존성은 변경하지 않는다.

### 범위 결정 근거

Node register/bootstrap 구현, smoke command, docs rewrite, runtime transport, OpenAI/A2A behavior는 제외한다. `resolveEdgeLogPath`의 bundle-local 기본 자체는 유지하고, setup 경로에서만 명시적인 service-writable path가 config에 들어가게 한다.

### 빌드 등급

build=`cloud-G07`, review=`cloud-G07`. CLI/setup/systemd config contract와 stdout template 검증이 얽힌 terminal workflow follow-up이다.

## 구현 체크리스트

- [ ] Setup-installed Edge config gets an explicit service-writable log path while `config init` keeps bundle-local empty log-path behavior.
- [ ] Regression tests cover setup template/dry-run log path and bundle `config init` new-field/empty-log-path output.
- [ ] `go test -count=1 ./packages/hostsetup ./apps/edge/cmd/edge ./apps/edge/internal/bootstrap` passes and bundle `config init`/`env` smoke still reports `<binary-dir>/logs/edge.log`.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_CONFIG-1] Setup-Safe Edge Log Path

#### 문제

`packages/hostsetup/templates.go:13`의 `edgeConfigTemplate`은 `logging.path`를 쓰지 않는다. 그런데 `apps/edge/cmd/edge/main.go:108`은 빈 log path를 `<binary-dir>/logs/edge.log`로 확정하고, setup unit은 `packages/hostsetup/systemd.go:17`의 `User=iop`로 `packages/hostsetup/systemd.go:19`의 binary path를 실행한다. 기본 설치 binary가 `/usr/local/bin/iop-edge`이면 service user가 `/usr/local/bin/logs`를 만들 수 없어 `serve`가 실패할 수 있다.

#### 해결 방법

`packages/hostsetup`에 setup용 template과 bundle-init용 template을 분리한다.

Before:

```go
// packages/hostsetup/templates.go:13
logging:
  level: "info"
  pretty: false
```

After:

```go
// packages/hostsetup/templates.go
const edgeConfigTemplate = `...
logging:
  level: "info"
  pretty: false
  path: "/var/lib/iop/edge/logs/edge.log"
...`

const edgeBundleConfigTemplate = `...
logging:
  level: "info"
  pretty: false
  path: ""
...`

func EdgeBundleConfigTemplate() string {
    return edgeBundleConfigTemplate
}
```

`apps/edge/cmd/edge/main.go:194`의 `config init`은 `hostsetup.EdgeSpec().ConfigTemplate` 대신 bundle template을 사용한다. 두 template 모두 `server.advertise_host`, `bootstrap.artifact_base_url`, `logging.path`를 명시해서 사용자가 새 설정 표면을 볼 수 있게 한다.

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

- [ ] `packages/hostsetup/templates.go`: setup template에는 `/var/lib/iop/edge/logs/edge.log`, bundle template에는 empty `logging.path`를 명시하고 bundle template accessor를 추가한다.
- [ ] `packages/hostsetup/setup_test.go`: `EdgeSpec().ConfigTemplate`이 service-writable log path와 새 config keys를 포함하는지 검증한다.
- [ ] `apps/edge/cmd/edge/main.go`: `config init`이 bundle template을 사용하도록 바꾼다.
- [ ] `apps/edge/cmd/edge/main_test.go`: `config init --stdout` 또는 output file이 새 keys와 empty `logging.path`를 포함하고 `/var/lib/iop/edge/logs/edge.log`를 포함하지 않는지 검증한다. 기존 setup dry-run 테스트에는 setup config preview의 service log path assertion을 추가한다.

#### 테스트 작성

Write or update:

- `packages/hostsetup/setup_test.go`: `TestEdgeSpecConfigTemplateUsesServiceWritableLogPath`
- `apps/edge/cmd/edge/main_test.go`: `TestConfigInitPrintsBundleTemplateWithEmptyLogPath`
- `apps/edge/cmd/edge/main_test.go`: extend `TestSetupDryRunUsesEdgeDefaults` for `/var/lib/iop/edge/logs/edge.log`

#### 중간 검증

```bash
go test -count=1 ./packages/hostsetup ./apps/edge/cmd/edge ./apps/edge/internal/bootstrap
```

Expected: pass; cache output is not acceptable.

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `packages/hostsetup/templates.go` | REVIEW_CONFIG-1 |
| `packages/hostsetup/setup_test.go` | REVIEW_CONFIG-1 |
| `apps/edge/cmd/edge/main.go` | REVIEW_CONFIG-1 |
| `apps/edge/cmd/edge/main_test.go` | REVIEW_CONFIG-1 |

## 최종 검증

```bash
go test -count=1 ./packages/hostsetup ./apps/edge/cmd/edge ./apps/edge/internal/bootstrap
```

```bash
tmp="$(mktemp -d)" && go build -o "$tmp/iop-edge" ./apps/edge/cmd/edge && (cd "$tmp" && ./iop-edge config init --force && ./iop-edge config check && ./iop-edge env)
```

Expected: tests pass; bundle smoke prints `OK edge.yaml`, `log_path: <tmp>/logs/edge.log`, and the setup template is not used for bundle `config init`.

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