<!-- task=m-oto-bootstrap-command-issue/01_oto_bootstrap_config plan=0 tag=CONFIG -->

# Plan - CONFIG

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

`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션 작성은 필수다. 구현 후 검증을 실행하고 실제 구현 내용과 stdout/stderr를 review stub에 기록한 뒤 active 파일을 그대로 두고 리뷰 준비 상태를 보고한다. 최종 판정, log rename, `complete.log`, archive 이동은 code-review 전용이다. 사용자 결정, 사용자 소유 외부 환경, scope 충돌로 막히면 review stub의 `사용자 리뷰 요청` 섹션에 정확한 결정과 증거를 남기고 멈춘다. 후속 에이전트가 재실행이나 증거 수집으로 해소할 수 있는 검증 공백은 사용자 리뷰 요청이 아니다.

## 배경

현재 `node register`는 Node artifact URL만 `bootstrap.artifact_base_url`에 보존한다. OTO bootstrap command는 OTO 소유 script URL과 release base URL을 함께 소비해야 하므로 Edge config가 두 값을 읽고 YAML patch가 보존할 수 있어야 한다. 이 선행 작업은 command 구현 전에 config 계약만 작게 고정한다.

## 사용자 리뷰 요청 흐름

구현 중 blocker는 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 섹션 형식은 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`를 따른다. code-review가 요청 정당성을 검증하고 실제 `USER_REVIEW.md` 작성 여부를 결정한다.

## 분석 결과

### 읽은 파일

- `agent-roadmap/current.md`
- `agent-roadmap/phase/automation-runtime-bridge/PHASE.md`
- `agent-roadmap/phase/automation-runtime-bridge/milestones/oto-bootstrap-command-issue.md`
- `agent-ops/rules/project/domain/edge/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/platform-common-smoke.md`
- `agent-test/local/testing-smoke.md`
- `apps/edge/cmd/edge/main.go`
- `apps/edge/cmd/edge/main_test.go`
- `packages/go/config/config.go`
- `packages/go/config/config_test.go`
- `configs/edge.yaml`
- `apps/edge/internal/node/mapper.go`
- `apps/edge/internal/node/store.go`
- `apps/edge/internal/transport/server.go`
- `../oto/assets/script/shell/oto_agent_bootstrap.sh`

### 테스트 환경 규칙

- `test_env=local`; `agent-test/local/rules.md`를 읽었다.
- 적용 profile: `agent-test/local/edge-smoke.md`, `agent-test/local/platform-common-smoke.md`, `agent-test/local/testing-smoke.md`.
- 적용 명령: edge 변경은 `go test ./apps/edge/...`, platform-common 변경은 `go test ./packages/go/...`, bootstrap UX 변경은 변경 entrypoint full-cycle 확인을 요구한다.
- 이 subtask의 최종 명령은 fresh 실행을 위해 `-count=1`을 사용한다. fallback 없음.

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

- `bootstrap.oto_bootstrap_script_url`, `bootstrap.oto_release_base_url` 로딩: 기존 테스트 없음. `packages/go/config/config_test.go`에 default/explicit load 테스트를 추가한다.
- YAML patch가 새 bootstrap 필드를 보존/갱신하는 동작: 기존 `artifact_base_url`만 다룬다. `apps/edge/cmd/edge/main_test.go`에 patch roundtrip 테스트를 추가한다.
- OTO agent adapterless config check는 작은 선행 변경으로 `TestConfigCheckAcceptsOTOAgentWithoutAdapters`가 추가되어 통과했다.

### 심볼 참조

- renamed/removed symbol: none.

### 분할 판단

- split decision policy를 plan 파일 선택 전에 평가했다.
- shared task group: `m-oto-bootstrap-command-issue`.
- `01_oto_bootstrap_config`: 독립 선행 작업. config struct/default/example/YAML patch helper를 다룬다.
- `02+01_agent_register_command`: `01` 완료 후 command surface가 새 config 계약을 소비한다.
- 이 plan은 predecessor가 없다. sibling `02+01_agent_register_command`는 이 subtask의 `complete.log`를 기다린다.

### 범위 결정 근거

- 포함: `packages/go/config`, `configs/edge.yaml`, `apps/edge/cmd/edge`의 YAML patch helper와 테스트.
- 제외: `agent register` command 구현, OTO bootstrap script 구현, release asset build/upload, checksum/signature, Control Plane UI.
- `../oto`는 script 인자 계약 확인용으로만 읽고 수정하지 않는다.

### 빌드 등급

- build=`local-G05`, review=`local-G05`. Config 계약과 YAML patch helper 중심의 bounded 변경이고 Go 테스트로 검증 가능하다.

## 구현 체크리스트

- [ ] [CONFIG-1] Edge bootstrap config에 OTO script/release URL 필드를 추가하고 load/default/example 테스트를 작성한다.
- [ ] [CONFIG-2] YAML patch helper가 `artifact_base_url`, `oto_bootstrap_script_url`, `oto_release_base_url`을 같은 방식으로 보존/갱신하게 만든다.
- [ ] [CONFIG-3] `go test -count=1 ./packages/go/config ./apps/edge/cmd/edge`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## CONFIG-1 Edge bootstrap config 필드

### 문제

`packages/go/config/config.go:86`의 `EdgeBootstrapConf`는 Node artifact URL만 가진다.

```go
86 type EdgeBootstrapConf struct {
87     ArtifactBaseURL string `mapstructure:"artifact_base_url" yaml:"artifact_base_url"`
88     Listen          string `mapstructure:"listen"            yaml:"listen"`
89     ArtifactDir     string `mapstructure:"artifact_dir"      yaml:"artifact_dir"`
90 }
```

OTO script는 필수 `--release-base-url`을 요구하고 HTTPS를 검증한다: `../oto/assets/script/shell/oto_agent_bootstrap.sh:16`, `../oto/assets/script/shell/oto_agent_bootstrap.sh:82`.

### 해결 방법

`EdgeBootstrapConf`에 아래 필드를 추가한다.

```go
OTOBootstrapScriptURL string `mapstructure:"oto_bootstrap_script_url" yaml:"oto_bootstrap_script_url"`
OTOReleaseBaseURL     string `mapstructure:"oto_release_base_url"     yaml:"oto_release_base_url"`
```

`setEdgeDefaults`에는 두 값을 빈 문자열 default로 둔다. `configs/edge.yaml`의 `bootstrap:` 주석과 예시에 두 필드를 추가한다.

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

- [ ] `packages/go/config/config.go`에 필드와 defaults를 추가한다.
- [ ] `packages/go/config/config_test.go`에 default empty와 explicit load 테스트를 추가한다.
- [ ] `configs/edge.yaml`의 `bootstrap:` 예시에 두 값을 빈 문자열로 추가한다.

### 테스트 작성

작성한다. `packages/go/config/config_test.go`에 `TestLoadEdge_OTOBootstrapDefaults`와 `TestLoadEdge_OTOBootstrapExplicit`를 추가한다. 목표는 default empty와 YAML explicit 값이 loader/runtime load에서 유지되는지 확인하는 것이다.

### 중간 검증

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

기대 결과: `ok iop/packages/go/config`.

## CONFIG-2 YAML patch helper 확장

### 문제

`apps/edge/cmd/edge/main.go:1163` 이후 `patchYAML`은 `bootstrap.artifact_base_url` 하나만 patch한다. 새 command가 OTO URL을 config에 기록하려면 같은 보존형 patch 경로가 필요하다.

```go
1163 // 2. Patch "bootstrap.artifact_base_url" if bootstrap config has artifact URL
1164 if cfg.Bootstrap.ArtifactBaseURL != "" {
...
1192     urlKey := &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: "artifact_base_url"}
1193     urlVal := &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: cfg.Bootstrap.ArtifactBaseURL}
```

### 해결 방법

`patchBootstrapStringFields(rootMap, map[string]string)` helper를 추가하고 `artifact_base_url`, `oto_bootstrap_script_url`, `oto_release_base_url` 중 값이 비어 있지 않은 필드만 patch한다. 기존 `artifact_base_url` 동작은 유지한다.

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

- [ ] `apps/edge/cmd/edge/main.go`에서 bootstrap patch 로직을 helper로 분리한다.
- [ ] `apps/edge/cmd/edge/main_test.go`에 OTO bootstrap fields patch 테스트를 추가한다.

### 테스트 작성

작성한다. `apps/edge/cmd/edge/main_test.go`에 `TestPatchYAMLUpdatesOTOBootstrapFields`를 추가한다. 목표는 기존 YAML layout을 보존하면서 `bootstrap.oto_bootstrap_script_url`과 `bootstrap.oto_release_base_url`이 추가/갱신되는지 확인하는 것이다.

### 중간 검증

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

기대 결과: `ok iop/apps/edge/cmd/edge`.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `packages/go/config/config.go` | CONFIG-1 |
| `packages/go/config/config_test.go` | CONFIG-1 |
| `configs/edge.yaml` | CONFIG-1 |
| `apps/edge/cmd/edge/main.go` | CONFIG-2 |
| `apps/edge/cmd/edge/main_test.go` | CONFIG-2 |

## 최종 검증

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

기대 결과: 모든 package가 exit code 0으로 종료한다.
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
