<!-- task=m-oto-bootstrap-command-issue/02+01_agent_register_command plan=0 tag=CLI -->

# Plan - CLI

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

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

## 배경

현재 Edge CLI는 generic node용 `node register`만 제공한다. OTO는 같은 token 기반 proto-socket registration path를 재사용하지만 bootstrap command는 OTO script와 release URL 인자를 소비해야 한다. 이 작업은 선행 config 계약이 완료된 뒤 Edge-local `agent register` command를 추가해 OTO agent record와 one-line bootstrap command를 만든다.

## 사용자 리뷰 요청 흐름

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

## Roadmap Targets

- Milestone: `agent-roadmap/phase/automation-runtime-bridge/milestones/oto-bootstrap-command-issue.md`
- Task ids:
  - `agent-register-cmd`: `iop-edge`가 OTO agent record를 만들거나 갱신하는 command 표면을 제공한다.
  - `node-interface-parity`: OTO agent command/config 표면이 기존 `iop-node` bootstrap의 필드명, flag 의미, 출력 형식, 진단 흐름을 최대한 유지한다.
  - `bootstrap-output`: command 출력은 한 줄 bootstrap command만 안정적으로 제공하고 token을 불필요하게 재출력하지 않는다.
  - `oto-args`: 출력 command가 OTO bootstrap 필수 인자 `--edge-url`, `--agent-id`, `--enrollment-token`, `--release-base-url`을 포함한다.
  - `config-roundtrip`: 생성된 edge config를 `config check`와 runtime load가 읽을 수 있다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `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`
- `../oto/lib/oto/agent/edge_registration_client.dart`
- `../oto/test/oto_agent_config_test.dart`

### 테스트 환경 규칙

- `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`.
- 적용 명령: `go test ./apps/edge/...`, `go test ./packages/go/...`, bootstrap/install UX 변경의 실제 entrypoint full-cycle command.
- 이 plan의 Go 검증은 fresh 실행을 위해 `-count=1`을 사용한다. fallback 없음.

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

- OTO `agent register` command: 기존 테스트 없음. 새 CLI tests가 필요하다.
- OTO bootstrap one-line output: 기존 node output tests만 있다. 새 exact-output test가 필요하다.
- OTO 필수 인자 포함: 기존 테스트 없음. command output assertion으로 검증한다.
- HTTP script URL gating: 기존 테스트 없음. boundary test가 필요하다.
- Config roundtrip: 작은 선행 변경으로 adapterless OTO config check는 통과했고, 이 plan은 generated config가 `config check`와 `config.LoadEdge`를 통과하는 테스트를 추가해야 한다.

### 심볼 참조

- renamed/removed symbol: none.

### 분할 판단

- split decision policy를 plan 파일 선택 전에 평가했다.
- shared task group: `m-oto-bootstrap-command-issue`.
- sibling subtasks:
  - `01_oto_bootstrap_config`: config struct/default/example/YAML patch 선행 계약.
  - `02+01_agent_register_command`: 이 plan. command surface가 `01`의 config fields와 patch helper를 소비한다.
- predecessor `01`: active plan exists at `agent-task/m-oto-bootstrap-command-issue/01_oto_bootstrap_config/PLAN-local-G05.md`; `complete.log` is not present yet. Runtime must wait for predecessor completion before implementing this task.

### 범위 결정 근거

- 포함: `apps/edge/cmd/edge` command surface, CLI tests, generated temp config full-cycle check.
- 제외: OTO bootstrap script 변경, OTO release asset build/upload, checksum/signature enforcement, Control Plane remote issue UI, OTO online smoke. Online smoke는 후속 Milestone이다.
- `../oto`는 script and config parser contract 확인용으로만 읽고 수정하지 않는다.

### 빌드 등급

- build=`cloud-G07`, review=`cloud-G07`. Public CLI workflow, one-line bootstrap output, URL policy, config mutation, full-cycle command evidence가 함께 걸려 terminal/CLI reasoning과 verification trust가 중요하다.

## 의존 관계 및 구현 순서

`02+01_agent_register_command`는 디렉터리 이름 기준으로 predecessor index `01`에 의존한다. `agent-task/m-oto-bootstrap-command-issue/01_oto_bootstrap_config/complete.log`가 생긴 뒤 구현한다. 추가 dependency는 없다.

## 구현 체크리스트

- [ ] [CLI-1] `iop-edge agent register [agent-id]` command를 추가하고 OTO agent record를 upsert한다.
- [ ] [CLI-2] OTO bootstrap URL/release URL/edge URL 정책과 one-line command renderer를 구현한다.
- [ ] [CLI-3] command help, output, config roundtrip, URL boundary, full-cycle command 검증 테스트를 작성한다.
- [ ] [CLI-4] `go test -count=1 ./apps/edge/cmd/edge ./packages/go/config`와 실제 `go run ./apps/edge/cmd/edge ... agent register ...` full-cycle command를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## CLI-1 Agent register command surface

### 문제

`apps/edge/cmd/edge/main.go:66`의 root command는 `nodeCmd()`만 등록하고 specialized agent command가 없다.

```go
66 root.AddCommand(serveCmd(), consoleCmd(), versionCmd(), configCmd(), envCmd(), setupCmd(), bootstrapCmd(), nodeCmd(), nodesCmd(), smokeCmd())
```

`nodeRegisterCmd`는 generic node adapter/runtime 설정을 전제로 한다: `apps/edge/cmd/edge/main.go:913`.

### 해결 방법

`agentCmd()`와 `agentRegisterCmd()`를 추가하고 root에 `agentCmd()`를 등록한다. Command shape:

```text
iop-edge agent register <agent-id> --bootstrap-script-url <url> --release-base-url <url>
```

Flags:

- `--alias` default `<agent-id>`
- `--token` default generated UUID, same meaning as `node register --token`
- `--edge-url` default `addressFor(resolveAdvertiseHost(cfg.Server.AdvertiseHost).Host, cfg.Server.Listen)`
- `--bootstrap-script-url` overrides and persists `bootstrap.oto_bootstrap_script_url`
- `--release-base-url` overrides and persists `bootstrap.oto_release_base_url`
- `--allow-insecure-bootstrap-url` allows local/dev `http://` script URL only when explicit

Upsert record:

```go
config.NodeDefinition{
    ID:        agentID,
    Alias:     alias,
    Token:     token,
    AgentKind: config.AgentKindOTOAgent,
}
```

Do not enable Node adapters for OTO records.

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

- [ ] `apps/edge/cmd/edge/main.go`에 `agentCmd`, `agentRegisterCmd`, OTO-specific flag variables를 추가한다.
- [ ] `apps/edge/cmd/edge/main.go` root command에 `agentCmd()`를 등록한다.
- [ ] Existing `node register` behavior는 변경하지 않는다.

### 테스트 작성

작성한다. `apps/edge/cmd/edge/main_test.go`에 `TestAgentRegisterUpsertsOTOAgentRecord`를 추가한다. 목표는 OTO record가 `agent_kind: oto-agent`, alias, token을 보존하고 adapter 없이 config check/load를 통과하는지 확인하는 것이다.

### 중간 검증

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

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

## CLI-2 One-line OTO bootstrap renderer and URL policy

### 문제

기존 `node register` 출력은 Node script와 positional token만 렌더링한다: `apps/edge/cmd/edge/main.go:1101`.

```go
1101 bootstrapURL := universalBootstrapScriptURL(artifactURL)
1102 fmt.Fprintf(cmd.OutOrStdout(), "curl -fsSL %s | bash -s %s\n", bootstrapURL, nodeDef.Token)
```

OTO script는 named 필수 인자 4개를 요구한다: `../oto/assets/script/shell/oto_agent_bootstrap.sh:16`, `../oto/assets/script/shell/oto_agent_bootstrap.sh:76`.

### 해결 방법

`renderOTOBootstrapCommand(opts)` helper를 추가한다. Output must be exactly one line and include:

```text
curl -fsSL <bootstrap-script-url> | bash -s -- --edge-url <edge-url> --agent-id <agent-id> --enrollment-token <token> --release-base-url <release-base-url> --agent-alias <alias>
```

Validation:

- script URL and release base URL must come from flag or config; missing values fail before YAML write.
- `release-base-url` must start with `https://`.
- script URL must start with `https://` unless `--allow-insecure-bootstrap-url` is set. `http://` is accepted only with that explicit flag.
- token appears only as the `--enrollment-token` value in the one-line command.

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

- [ ] `apps/edge/cmd/edge/main.go`에 OTO URL resolver/validator를 추가한다.
- [ ] `apps/edge/cmd/edge/main.go`에 one-line renderer를 추가한다.
- [ ] `apps/edge/cmd/edge/main_test.go`에 exact output and URL boundary tests를 추가한다.

### 테스트 작성

작성한다. Tests:

- `TestAgentRegisterPrintsOneLineOTOCommand`
- `TestAgentRegisterRejectsInsecureBootstrapURLWithoutFlag`
- `TestAgentRegisterRejectsInsecureReleaseBaseURL`
- `TestAgentRegisterAllowsInsecureBootstrapURLWithExplicitFlag`

### 중간 검증

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

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

## CLI-3 Help, config roundtrip, and full-cycle command evidence

### 문제

Root help currently advertises only `node register`: `apps/edge/cmd/edge/main.go:49`, and help tests assert the same at `apps/edge/cmd/edge/main_test.go:1099`. The milestone requires OTO agent command/config parity and generated config roundtrip.

### 해결 방법

Update help without replacing the existing generic node flow. Add OTO agent register as an optional specialized agent path. Tests must assert command discovery and no legacy script path leakage.

Generated config roundtrip test should run command against a temp `edge.yaml`, then:

- parse YAML and assert OTO URL config fields are persisted,
- call `config.LoadEdge`,
- run `config check`,
- assert output is exactly one command line.

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

- [ ] `apps/edge/cmd/edge/main.go` root/help examples mention `agent register` as OTO specialized path.
- [ ] `apps/edge/cmd/edge/main_test.go` updates command discovery/help tests.
- [ ] `apps/edge/cmd/edge/main_test.go` adds generated config roundtrip test.

### 테스트 작성

작성한다. `TestHelpDiscoversEdgeLocalDevFlow`에 `agent register` command discovery를 추가하고, a full roundtrip test를 새로 추가한다.

### 중간 검증

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

기대 결과: both packages pass.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/edge/cmd/edge/main.go` | CLI-1, CLI-2, CLI-3 |
| `apps/edge/cmd/edge/main_test.go` | CLI-1, CLI-2, CLI-3 |

## 최종 검증

```bash
go test -count=1 ./apps/edge/cmd/edge ./packages/go/config
go test -count=1 ./apps/edge/... ./packages/go/...
tmpdir="$(mktemp -d)"; printf 'server:\n  listen: "127.0.0.1:19090"\n  advertise_host: "edge.example.test"\nbootstrap:\n  oto_bootstrap_script_url: "https://oto.example.test/bootstrap/oto_agent_bootstrap.sh"\n  oto_release_base_url: "https://oto.example.test/releases"\nnodes: []\n' > "$tmpdir/edge.yaml"; go run ./apps/edge/cmd/edge --config "$tmpdir/edge.yaml" agent register oto-agent-a --token test-token; go run ./apps/edge/cmd/edge --config "$tmpdir/edge.yaml" config check
rg --sort path -n "agent register|oto_bootstrap_script_url|oto_release_base_url|--edge-url|--release-base-url" apps/edge/cmd/edge packages/go/config configs agent-roadmap/phase/automation-runtime-bridge/milestones/oto-bootstrap-command-issue.md
```

기대 결과: Go tests pass; full-cycle command prints one bootstrap line then config check prints `OK`; search output shows command/config/milestone anchors.
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
