<!-- task=edge_bootstrap plan=0 tag=API -->

# Edge Bootstrap Linux Script 구현 계획

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

`CODE_REVIEW-cloud-G07.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우는 것이 필수다. 구현 후 검증을 실행하고, 실제 notes/output을 채우고, active 파일은 그대로 둔 채 리뷰 준비 완료를 보고한다. 최종 판정, log rename, `complete.log`, archive 이동은 code-review-skill 전용이다.

## 배경

Edge bootstrap 계약은 Linux 대상 머신에 OTO가 없다는 전제에서 release asset을 다운로드하고 `~/.oto/agent/config.yaml`을 만든 뒤 agent 실행을 시작해야 한다. Milestone 문서에는 command shape, arch 선택, config path, background 실행, security mode 기준이 정리되었다. 이 계획은 그 계약을 실제 repository asset과 검증 가능한 test로 옮기는 큰 구현 작업만 분리한다.

## 분석 결과

### 읽은 파일

- `agent-ops/roadmap/current.md`
- `agent-ops/roadmap/ROADMAP.md`
- `agent-ops/roadmap/milestones/edge-bootstrap-contract.md`
- `agent-ops/roadmap/milestones/oto-agent-registration.md`
- `agent-ops/rules/project/domain/cli/rules.md`
- `agent-ops/rules/project/domain/framework/rules.md`
- `agent-ops/rules/project/domain/core/rules.md`
- `assets/script/shell/build.sh`
- `assets/script/shell/check_package_update.sh`
- `assets/script/shell/path_importer.sh`
- `assets/script/shell/jenkins_env_params.sh`
- `assets/package/oto_install.ps1`
- `assets/package/oto_update.bat`
- `assets/package/chocolatey/tools/chocolateyinstall.ps1`
- `pubspec.yaml`
- `analysis_options.yaml`
- `bin/main.dart`
- `lib/cli/commands/command_start.dart`
- `lib/cli/commands/command_const.dart`
- `lib/oto/core/system_runtime.dart`
- `test/oto_system_runtime_test.dart`
- `test/oto_iop_connection_smoke_test.dart`

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

- Linux bootstrap script asset 생성: 기존 테스트 없음. 새 `test/oto_agent_bootstrap_script_test.dart`로 script syntax와 계약 문자열을 검증한다.
- Arch mapping: 기존 테스트 없음. 새 테스트에서 `x86_64|amd64 -> oto-linux-x64.tar.gz`, `aarch64|arm64 -> oto-linux-arm64.tar.gz`, unsupported arch failure branch가 script에 존재하는지 검증한다.
- Config path와 필수 YAML key: 기존 테스트 없음. 새 테스트에서 `config.yaml`, `agent.id`, `agent.enrollment_token`, `edge.url`, `runtime.workspace_root` 문자열을 검증한다.
- Background 실행 command: 기존 테스트 없음. 새 테스트에서 `oto agent run --config`, pid file, log path, `--no-background` branch를 검증한다.

### 심볼 참조

- renamed/removed symbols: none.
- 새 파일 추가만 수행한다. 기존 Dart symbol call site 변경은 없다.

### 분할 판단

분할 정책을 먼저 평가했다. 이 작업은 shell asset 1개와 해당 asset을 읽는 contract test 1개가 같은 산출물을 검증하는 단일 구현 단위다. shared API foundation과 broad call-site rollout이 분리되지 않고, storage/concurrency/protocol 구현도 포함하지 않는다. script와 test를 분리하면 `complete.log` 단위가 작아지는 이점보다 같은 계약을 두 번 조율하는 비용이 커서 single-plan으로 둔다.

### 범위 결정 근거

- 포함: `assets/script/shell/oto_agent_bootstrap.sh` 신규 추가, `test/oto_agent_bootstrap_script_test.dart` 신규 추가.
- 제외: 실제 `oto agent run` CLI 구현은 [`oto-agent` 등록 흐름](../../agent-ops/roadmap/milestones/oto-agent-registration.md) 범위다.
- 제외: Edge 서버 API, Edge UI, registry, credential 발급은 iop 책임 경계다.
- 제외: checksum/signature, Edge fingerprint, credential rotation, systemd user service 설치는 후속 보안 강화 범위다.
- 제외: Windows/macOS bootstrap은 이 Milestone의 범위 제외에 명시되어 있다.

### 빌드 등급

- build lane: `cloud-G07`
- review lane: `cloud-G07`
- 근거: shell/CLI workflow, install script orchestration, stdout/stderr/log/pid path 계약을 다루는 terminal-agent 성격이며 기존 테스트가 없다.

## 구현 체크리스트

- [ ] [API-1] Linux bootstrap script asset을 계약 기준에 맞게 추가한다.
- [ ] [API-2] Bootstrap script 계약 테스트를 추가한다.
- [ ] 모든 중간 검증과 최종 검증을 실행하고 실제 출력을 `CODE_REVIEW-cloud-G07.md`에 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [API-1] Linux bootstrap script asset 추가

#### 문제

`agent-ops/roadmap/milestones/edge-bootstrap-contract.md:23`은 Linux 대상 bootstrap script 발급 흐름을 범위로 두지만 repository에는 해당 Linux bootstrap script asset이 없다. 기존 shell assets는 Jenkins/package helper 중심이다: `assets/script/shell/build.sh:1`, `assets/script/shell/check_package_update.sh:1`, `assets/script/shell/path_importer.sh:1`.

#### 해결 방법

`assets/script/shell/oto_agent_bootstrap.sh`를 새로 추가한다. 기존 script처럼 bash shebang을 사용하되, installer 성격이므로 `set -euo pipefail`과 명시적 arg parser를 둔다.

Before:

```text
assets/script/shell/oto_agent_bootstrap.sh 없음
```

After:

```bash
#!/usr/bin/env bash
set -euo pipefail

edge_url=""
agent_id=""
enrollment_token=""
release_base_url=""
agent_alias=""
install_dir="${HOME}/.oto/bin"
config_path="${HOME}/.oto/agent/config.yaml"
workspace_root="${HOME}/.oto/workspace"
log_dir="${HOME}/.oto/agent/log"
background="true"
```

구현 세부:

- `--edge-url`, `--agent-id`, `--enrollment-token`, `--release-base-url` 누락 시 usage와 함께 실패한다.
- `uname -m`을 `x86_64|amd64 -> oto-linux-x64.tar.gz`, `aarch64|arm64 -> oto-linux-arm64.tar.gz`로 매핑한다.
- 다운로드 URL은 `${release_base_url}/${asset_name}`로 구성한다.
- `curl -fsSL`로 임시 디렉터리에 다운로드하고 `tar -xzf`로 압축을 푼 뒤 `oto` 실행 파일을 `install_dir/oto`로 설치한다.
- `config_path` 상위 디렉터리, `workspace_root`, `log_dir`를 생성한다.
- YAML config에는 `agent.id`, `agent.alias`, `agent.enrollment_token`, `edge.url`, `runtime.install_dir`, `runtime.workspace_root`, `runtime.log_dir`를 쓴다.
- config 작성 후 가능한 경우 `chmod 600 "$config_path"`와 `chmod +x "$install_dir/oto"`를 수행한다.
- 기본값은 background 실행이며 `nohup "$install_dir/oto" agent run --config "$config_path" >> "$log_dir/oto-agent.log" 2>&1 &` 후 pid를 `~/.oto/agent/oto-agent.pid`에 쓴다.
- `--no-background`가 있으면 background 시작 대신 foreground 재실행 command를 출력한다.
- token 값은 성공/실패 로그에 직접 출력하지 않는다.

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

- [ ] `assets/script/shell/oto_agent_bootstrap.sh`를 추가한다.
- [ ] 필수/선택 인자 parser와 usage를 구현한다.
- [ ] arch mapping과 unsupported arch failure를 구현한다.
- [ ] download/extract/install/config/background/no-background 흐름을 구현한다.
- [ ] token이 echo되지 않도록 로그 문구를 점검한다.

#### 테스트 작성

직접 test file은 [API-2]에서 작성한다. 이 항목의 중간 검증은 shell parser 수준의 syntax 검증으로 둔다.

#### 중간 검증

```bash
bash -n assets/script/shell/oto_agent_bootstrap.sh
```

기대 결과: exit code 0, stderr 없음.

### [API-2] Bootstrap script 계약 테스트 추가

#### 문제

새 bootstrap script는 shell asset이라 Dart analyzer만으로 계약 누락을 잡기 어렵다. 기존 test pattern은 fake runtime과 actual process smoke를 사용하지만, 이 작업은 외부 다운로드를 실행하지 않고 asset contract를 deterministic하게 확인해야 한다. 참고 파일: `test/oto_system_runtime_test.dart:1`, `test/oto_iop_connection_smoke_test.dart:31`.

#### 해결 방법

`test/oto_agent_bootstrap_script_test.dart`를 추가해 script 내용을 읽고 다음 계약을 검증한다.

Before:

```text
test/oto_agent_bootstrap_script_test.dart 없음
```

After:

```dart
import 'dart:io';

import 'package:test/test.dart';

void main() {
  final script = File('assets/script/shell/oto_agent_bootstrap.sh');

  test('linux bootstrap script keeps required contract tokens', () async {
    final content = await script.readAsString();

    expect(content, contains('--edge-url'));
    expect(content, contains('--agent-id'));
    expect(content, contains('--enrollment-token'));
    expect(content, contains('--release-base-url'));
  });
}
```

테스트 항목:

- required flags: `--edge-url`, `--agent-id`, `--enrollment-token`, `--release-base-url`.
- optional/defaults: `--install-dir`, `--config-path`, `--workspace-root`, `--log-dir`, `--no-background`, `$HOME/.oto/bin`, `$HOME/.oto/agent/config.yaml`.
- arch assets: `oto-linux-x64.tar.gz`, `oto-linux-arm64.tar.gz`.
- config keys: `agent:`, `id:`, `enrollment_token:`, `edge:`, `url:`, `runtime:`, `workspace_root:`.
- runtime command: `agent`, `run`, `--config`, `nohup`, `oto-agent.pid`.

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

- [ ] `test/oto_agent_bootstrap_script_test.dart`를 추가한다.
- [ ] 계약 문자열 테스트를 의미 단위로 나눠 작성한다.
- [ ] 테스트가 다운로드나 background process를 실제 실행하지 않게 유지한다.

#### 테스트 작성

작성한다. 경로는 `test/oto_agent_bootstrap_script_test.dart`, 테스트 이름은 `linux bootstrap script keeps required contract tokens` 및 필요 시 세부 테스트로 분리한다. 목표는 shell script의 public bootstrap contract가 실수로 빠지지 않게 하는 것이다.

#### 중간 검증

```bash
dart test test/oto_agent_bootstrap_script_test.dart
```

기대 결과: 모든 테스트 통과.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `assets/script/shell/oto_agent_bootstrap.sh` | API-1 |
| `test/oto_agent_bootstrap_script_test.dart` | API-2 |

## 최종 검증

```bash
bash -n assets/script/shell/oto_agent_bootstrap.sh
dart test test/oto_agent_bootstrap_script_test.dart
dart analyze
rg --sort path -n "oto_agent_bootstrap|release-base-url|oto-linux-x64|oto-linux-arm64|config.yaml" assets/script test agent-ops/roadmap/milestones/edge-bootstrap-contract.md
```

기대 결과: 각 명령 exit code 0. `dart test`는 fresh 실행 출력이어야 하며 캐시 출력으로 대체하지 않는다. `rg`는 관련 계약 문자열을 script, test, Milestone 문서에서 보여야 한다.

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