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

# Code Review Reference - CLI

> **[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, user-owned 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`. Evidence gaps that a follow-up agent can close by rerunning commands or collecting artifacts are normal follow-up issues, not user-review blockers by themselves.
> 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-06-03
task=m-oto-bootstrap-command-issue/02+01_agent_register_command, plan=0, tag=CLI

## 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

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

> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.

각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.

1. 판정을 append한다.
2. `CODE_REVIEW-{review_lane}-GNN.md` -> `code_review_{review_lane}_GNN_N.log`, `PLAN-{build_lane}-GNN.md` -> `plan_{build_lane}_GNN_M.log`로 아카이브한다.
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/{task_name}/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. `USER_REVIEW.md`가 사용자 결정으로 완료/PASS 해소되면 code-review가 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log` 작성 후 archive 이동한다.
4. PASS이고 task group이 `m-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다.
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.

---

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [CLI-1] `iop-edge agent register [agent-id]` command를 추가하고 OTO agent record를 upsert한다. | [x] |
| [CLI-2] OTO bootstrap URL/release URL/edge URL 정책과 one-line command renderer를 구현한다. | [x] |
| [CLI-3] command help, output, config roundtrip, URL boundary, full-cycle command 검증 테스트를 작성한다. | [x] |

## 구현 체크리스트

- [x] [CLI-1] `iop-edge agent register [agent-id]` command를 추가하고 OTO agent record를 upsert한다.
- [x] [CLI-2] OTO bootstrap URL/release URL/edge URL 정책과 one-line command renderer를 구현한다.
- [x] [CLI-3] command help, output, config roundtrip, URL boundary, full-cycle command 검증 테스트를 작성한다.
- [x] [CLI-4] `go test -count=1 ./apps/edge/cmd/edge ./packages/go/config`와 실제 `go run ./apps/edge/cmd/edge ... agent register ...` full-cycle command를 실행한다.
- [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_{review_lane}_GNN_N.log`로 아카이브한다.
- [x] active `PLAN-*-G??.md`를 `plan_{build_lane}_GNN_M.log`로 아카이브한다.
- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하여 plan/review/archive 산출물이 추적 가능한지 확인한다.
- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [x] PASS이면 active task 디렉터리 `agent-task/m-oto-bootstrap-command-issue/02+01_agent_register_command/`를 `agent-task/archive/YYYY/MM/m-oto-bootstrap-command-issue/02+01_agent_register_command/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [x] PASS이고 task group이 `m-oto-bootstrap-command-issue`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [x] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-oto-bootstrap-command-issue/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-{build_lane}-GNN.md`와 `CODE_REVIEW-{review_lane}-GNN.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로 이동한다.

## 계획 대비 변경 사항

계획에 명시된 모든 설계와 기능을 준수하여 구현하였으며, 특이 사항이나 계획 대비 변경 사항은 없습니다.

## 주요 설계 결정

- **URL 스키마 검증 강제화**: `release-base-url`은 `https://`로 시작하지 않을 시 에러를 유발하여 보안 정책을 고수했습니다. `bootstrap-script-url`은 기본으로 `https://`를 사용하되, 로컬/개발 편의성을 고려해 `--allow-insecure-bootstrap-url` 플래그가 수동으로 지정되었을 경우에만 `http://`도 사용 가능하게 통제하였습니다.
- **OTO Node 구조 통일**: OTO agent record가 upsert될 때, `agent_kind`는 `oto-agent`로 저장되며 adapters는 전부 disabled/empty 상태가 됩니다. 이는 edge-node 공통 validation 흐름에 영향을 주지 않으면서 Specialized Agent의 요구사항을 반영하는 최소 침습적 구조 설계입니다.

## 사용자 리뷰 요청

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

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

- `agent-task/m-oto-bootstrap-command-issue/01_oto_bootstrap_config/complete.log`가 존재한 뒤 구현됐는가.
- `agent register`가 OTO record를 `agent_kind=oto-agent`로 저장하고 generic node path를 깨지 않는가.
- 출력이 정확히 한 줄이고 OTO 필수 인자 4개를 포함하며 token을 추가로 출력하지 않는가.
- HTTPS 기본 정책과 explicit local/dev HTTP override가 테스트로 고정됐는가.
- `Roadmap Targets`의 5개 task id가 PASS 근거와 일치하는가.

## 검증 결과

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

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

### CLI-3 중간 검증
```bash
$ go test -count=1 ./apps/edge/cmd/edge ./packages/go/config
ok      iop/apps/edge/cmd/edge  0.069s
ok      iop/packages/go/config  0.018s
```

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

```bash
$ go test -count=1 ./apps/edge/... ./packages/go/...
ok      iop/apps/edge/cmd/edge  0.098s
ok      iop/apps/edge/internal/bootstrap        0.033s
ok      iop/apps/edge/internal/controlplane     4.454s
ok      iop/apps/edge/internal/events   0.016s
ok      iop/apps/edge/internal/input    0.028s
ok      iop/apps/edge/internal/input/a2a        0.016s
ok      iop/apps/edge/internal/node     0.013s
ok      iop/apps/edge/internal/openai   1.517s
ok      iop/apps/edge/internal/opsconsole       0.035s
ok      iop/apps/edge/internal/service  0.013s
ok      iop/apps/edge/internal/transport        0.014s
ok      iop/packages/go/audit   0.003s
?       iop/packages/go/auth    [no test files]
ok      iop/packages/go/config  0.015s
?       iop/packages/go/events  [no test files]
ok      iop/packages/go/hostsetup       0.012s
?       iop/packages/go/jobs    [no test files]
?       iop/packages/go/metadata        [no test files]
ok      iop/packages/go/observability   0.024s
?       iop/packages/go/policy  [no test files]
?       iop/packages/go/version [no test files]
```

```bash
$ 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
curl -fsSL https://oto.example.test/bootstrap/oto_agent_bootstrap.sh | bash -s -- --edge-url edge.example.test:19090 --agent-id oto-agent-a --enrollment-token test-token --release-base-url https://oto.example.test/releases --agent-alias oto-agent-a
OK /tmp/tmp.CYgEwfuROj/edge.yaml
```

```bash
$ 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
apps/edge/cmd/edge/main.go
52:  3. node register  - Add a node record to edge.yaml and generate its one-line bootstrap command. (Use 'agent register' for specialized OTO agents.)
60:  iop-edge agent register my-agent --bootstrap-script-url https://example.com/boot.sh --release-base-url https://example.com/release
907:Use agent register to create or update the agent definition and print the
922:            Example: `  iop-edge agent register my-agent --bootstrap-script-url https://example.com/boot.sh --release-base-url https://example.com/release
952:                            return errors.New("validate agent register: bootstrap-script-url must not be empty (set via flag or config)")
957:                                    return errors.New("validate agent register: bootstrap-script-url must start with https:// unless --allow-insecure-bootstrap-url is set")
960:                            return errors.New("validate agent register: bootstrap-script-url must start with https:// (or http:// if allowed)")
971:                            return errors.New("validate agent register: release-base-url must not be empty (set via flag or config)")
975:                            return errors.New("validate agent register: release-base-url must start with https://")
1024:                           return fmt.Errorf("validate agent register: %w", err)
1035:                   // 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>
1036:                   fmt.Fprintf(cmd.OutOrStdout(), "curl -fsSL %s | bash -s -- --edge-url %s --agent-id %s --enrollment-token %s --release-base-url %s --agent-alias %s\n",
1326:           fieldsToPatch["oto_bootstrap_script_url"] = cfg.Bootstrap.OTOBootstrapScriptURL
1329:           fieldsToPatch["oto_release_base_url"] = cfg.Bootstrap.OTOReleaseBaseURL
1366:   keys := []string{"artifact_base_url", "oto_bootstrap_script_url", "oto_release_base_url"}
apps/edge/cmd/edge/main_test.go
1145:                   want: []string{"agent register", "edge.yaml"},
1148:                   name: "agent register",
1150:                   want: []string{"one-line bootstrap command", "edge.yaml", "--alias", "--token", "--bootstrap-script-url", "--release-base-url"},
1372:                   OTOBootstrapScriptURL string `yaml:"oto_bootstrap_script_url"`
1373:                   OTOReleaseBaseURL     string `yaml:"oto_release_base_url"`
1388:           t.Errorf("oto_bootstrap_script_url = %q", doc.Bootstrap.OTOBootstrapScriptURL)
1391:           t.Errorf("oto_release_base_url = %q", doc.Bootstrap.OTOReleaseBaseURL)
1402:  oto_bootstrap_script_url: "https://example.com/boot.sh"
1403:  oto_release_base_url: "https://example.com/releases"
1417:           t.Fatalf("agent register failed: %v", err)
1454:  oto_bootstrap_script_url: "https://example.com/boot.sh"
1455:  oto_release_base_url: "https://example.com/releases"
1466:   root.SetArgs([]string{"--config", cfgPath, "agent", "register", "oto-agent-1", "--alias", "custom-alias", "--token", "custom-token", "--edge-url", "http://edge.test:9090"})
1469:           t.Fatalf("agent register failed: %v", err)
1473:   want := "curl -fsSL https://example.com/boot.sh | bash -s -- --edge-url http://edge.test:9090 --agent-id oto-agent-1 --enrollment-token custom-token --release-base-url https://example.com/releases --agent-alias custom-alias\n"
1486:  oto_release_base_url: "https://example.com/releases"
1515:  oto_bootstrap_script_url: "https://example.com/boot.sh"
1526:   root.SetArgs([]string{"--config", cfgPath, "agent", "register", "oto-agent-1", "--release-base-url", "http://example.com/releases"})
1544:  oto_release_base_url: "https://example.com/releases"
1558:           t.Fatalf("agent register failed with flag: %v", err)
1582:   // 2. agent register
1591:           "--release-base-url", "https://oto.example.test/releases",
1593:           "--edge-url", "localhost:9090",
1596:           t.Fatalf("agent register failed: %v", err)
1601:   wantCmd := "curl -fsSL https://oto.example.test/bootstrap/oto_agent_bootstrap.sh | bash -s -- --edge-url localhost:9090 --agent-id oto-agent-test --enrollment-token test-token --release-base-url https://oto.example.test/releases --agent-alias oto-agent-test\n"
packages/go/config/config.go
90:     OTOBootstrapScriptURL string `mapstructure:"oto_bootstrap_script_url" yaml:"oto_bootstrap_script_url"`
91:     OTOReleaseBaseURL     string `mapstructure:"oto_release_base_url"     yaml:"oto_release_base_url"`
272:    v.SetDefault("bootstrap.oto_bootstrap_script_url", "")
273:    v.SetDefault("bootstrap.oto_release_base_url", "")
packages/go/config/config_test.go
736:            t.Errorf("expected empty bootstrap.oto_bootstrap_script_url by default, got %q", cfg.Bootstrap.OTOBootstrapScriptURL)
739:            t.Errorf("expected empty bootstrap.oto_release_base_url by default, got %q", cfg.Bootstrap.OTOReleaseBaseURL)
749:  oto_bootstrap_script_url: "https://example.com/bootstrap.sh"
750:  oto_release_base_url: "https://example.com/releases"
760:            t.Errorf("bootstrap.oto_bootstrap_script_url=%q", cfg.Bootstrap.OTOBootstrapScriptURL)
763:            t.Errorf("bootstrap.oto_release_base_url=%q", cfg.Bootstrap.OTOReleaseBaseURL)
configs/edge.yaml
16:  oto_bootstrap_script_url: ""
17:  oto_release_base_url: ""
agent-roadmap/phase/automation-runtime-bridge/milestones/oto-bootstrap-command-issue.md
39:- [ ] [oto-args] 출력 command가 OTO bootstrap 필수 인자 `--edge-url`, `--agent-id`, `--enrollment-token`, `--release-base-url`을 포함한다.
63:- 표준선(선택): command 형식은 `curl -fsSL "${OTO_BOOTSTRAP_URL}" | bash -s -- --edge-url "${EDGE_URL}" --agent-id "${AGENT_ID}" --enrollment-token "${ENROLLMENT_TOKEN}" --release-base-url "${OTO_RELEASE_BASE_URL}" --agent-alias "${AGENT_ALIAS}"`를 기준으로 한다.
```

---

> **[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.

## 코드리뷰 결과

### 종합 판정

PASS

### 차원별 평가

| 차원 | 평가 | 근거 |
|------|------|------|
| correctness | Pass | `agent register`가 OTO record를 upsert하고 `agent_kind=oto-agent`, adapter disabled 상태, one-line OTO bootstrap command를 계획대로 생성한다. |
| completeness | Pass | CLI-1~CLI-4 범위와 Roadmap Targets의 command/config/output 요구사항을 모두 충족했다. |
| test coverage | Pass | command, help, URL boundary, YAML patch, generated config roundtrip 테스트가 있으며 review에서 대상/확장/전체 Go 테스트와 실제 CLI full-cycle을 재실행했다. |
| API contract | Pass | 기존 `node register` 경로를 유지하고 OTO script/parser의 필수 인자 계약과 config loader/default 계약을 유지한다. |
| code quality | Pass | 기존 Cobra command, YAML patch, config validation 패턴을 확장했고 불필요한 구조 변경이 없다. |
| plan deviation | Pass | 계획 대비 범위 이탈 없음. |
| verification trust | Pass | 구현 문서의 검증 출력과 review 재실행 결과가 일치하고, 추가로 generated config persistence를 실제 command로 확인했다. |

### 발견된 문제

없음

### 다음 단계

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