<!-- task=m-oto-registration-online-smoke/02+01_oto_agent_session plan=0 tag=API -->

# OTO Agent Persistent Registration Session

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

`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채우는 것까지가 구현입니다. 검증 출력은 실제 stdout/stderr를 붙이고 active 파일을 남긴 채 리뷰 요청 상태로 보고하세요. 종결, archive, `complete.log` 작성은 code-review 스킬 전용입니다.

## 배경

OTO `EdgeRegistrationClient.register()`는 registration response를 받은 뒤 client를 즉시 닫는다. iop Milestone은 registration accepted 이후 첫 heartbeat까지 살아 있는 OTO agent를 요구하므로, OTO agent 도메인에 persistent session 경계가 필요하다. 현재 OTO tests는 `protobuf ^3.1.0`과 `proto_socket`의 `protobuf ^6.0.0` 요구가 충돌해 baseline 실행도 막힌다.

## 사용자 리뷰 요청 흐름

구현 중 사용자-only blocker가 있으면 active review stub의 `사용자 리뷰 요청` 섹션에 정확한 결정, 근거, 재개 조건을 남기고 멈춘다. dependency solving이나 test rerun으로 해소 가능한 증거 공백은 사용자 리뷰 요청이 아니다.

## 분석 결과

### 읽은 파일

- `agent-roadmap/phase/automation-runtime-bridge/milestones/oto-registration-online-smoke.md`
- `../oto/AGENTS.md`
- `../oto/agent-ops/rules/project/rules.md`
- `../oto/agent-ops/rules/project/domain/agent/rules.md`
- `../oto/agent-ops/rules/project/domain/framework/rules.md`
- `../oto/pubspec.yaml`
- `../oto/lib/oto/agent/edge_registration_client.dart`
- `../oto/lib/oto/agent/agent_runner.dart`
- `../oto/lib/oto/agent/agent_config.dart`
- `../oto/lib/cli/commands/command_agent.dart`
- `../oto/test/oto_agent_registration_test.dart`
- `../oto/test/oto_agent_config_test.dart`
- `../oto/test/oto_iop_connection_smoke_test.dart`
- `../proto-socket/go/base_client.go`
- `../proto-socket/go/tcp_client.go`
- `../proto-socket/go/communicator.go`

### 테스트 환경 규칙

- OTO `agent-test/local/rules.md`는 없다.
- 적용 fallback: OTO agent domain rule의 `dart analyze`와 agent 관련 tests.
- preflight: `command -v dart` 성공, Dart SDK `3.11.3`.
- preflight failure: `cd ../oto && dart test test/oto_agent_registration_test.dart test/oto_agent_config_test.dart`는 `protobuf ^3.1.0`과 path dependency `proto_socket`의 `protobuf ^6.0.0` 충돌로 exit code 65.

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

- OTO persistent session: 없음. `EdgeRegistrationClient.register()`는 [edge_registration_client.dart](/config/workspace/oto/lib/oto/agent/edge_registration_client.dart:118)에서 즉시 close한다.
- Runner daemon behavior: `DefaultAgentRunner.run()`은 registration 성공 로그 후 return한다.
- Existing smoke: `oto_iop_connection_smoke_test.dart`는 accepted response와 runtime config만 확인하고 heartbeat/online evidence는 없다.
- Dependency baseline: tests가 dependency solving 전에 막혀 test coverage가 실행 불가 상태다.

### 심볼 참조

- 변경 후보: `EdgeRegistrationClient.register` override/call sites는 `../oto/test/oto_agent_registration_test.dart`, `../oto/test/oto_iop_connection_smoke_test.dart`, `../oto/lib/oto/agent/agent_runner.dart`에 있다.
- 제거하지 말 것: 기존 one-shot `register()` API는 unit test와 runner injection 호환을 위해 유지하거나 wrapper로 보존한다.

### 분할 판단

- 이 subtask는 `02+01_oto_agent_session`이며 predecessor `01_iop_edge_lifecycle` 완료가 필요하다.
- predecessor check: 아직 `agent-task/m-oto-registration-online-smoke/01_iop_edge_lifecycle/complete.log` 없음. 구현 시작 전 active/archive candidate `01_*` complete.log를 확인해야 한다.
- split 이유: OTO dependency/session 변경은 iop Edge lifecycle foundation과 repo/domain이 다르고 검증 실패 원인이 다르다.

### 범위 결정 근거

- OTO pipeline build/run payload, artifact/log stream은 Milestone 범위 제외이므로 구현하지 않는다.
- scheduler/background service manager 정책은 건드리지 않는다.
- generated protobuf Dart 파일은 직접 수정하지 않는다.
- iop Edge code는 predecessor subtask에서만 수정한다.

### 빌드 등급

- build: `cloud-G06`, review: `cloud-G06`. Dart agent lifecycle과 dependency alignment가 cross-repo로 묶여 있고, tests baseline blocker 해소가 필요하다.

## 의존 관계 및 구현 순서

이 subtask는 디렉터리명 `02+01_oto_agent_session`에 따라 `01_iop_edge_lifecycle`의 `complete.log`가 필요하다. 구현 시작 전 아래 중 정확히 하나를 확인한다.

- active: `agent-task/m-oto-registration-online-smoke/01_*/complete.log`
- archive: `agent-task/archive/*/*/m-oto-registration-online-smoke/01_*/complete.log`

## 구현 체크리스트

- [x] `../oto/pubspec.yaml`의 protobuf constraint를 path dependency `proto_socket`과 맞춰 tests가 resolve되게 한다.
- [x] 기존 `register()` 호환을 유지하면서 close하지 않는 OTO registration session API를 추가한다.
- [x] `DefaultAgentRunner.run()`이 registration 성공 후 agent connection을 유지하고 종료 신호 또는 cancellation에서 닫도록 조정한다.
- [x] OTO agent unit tests가 persistent session API와 rejected registration behavior를 검증하게 한다.
- [x] `cd ../oto && dart test test/oto_agent_registration_test.dart test/oto_agent_config_test.dart`를 실행한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [API-1] Dependency Alignment

문제: [pubspec.yaml](/config/workspace/oto/pubspec.yaml:23)은 `proto_socket` path dependency를 쓰고, [pubspec.yaml](/config/workspace/oto/pubspec.yaml:30)은 `protobuf ^3.1.0`을 고정한다. 현재 tests는 `proto_socket`의 `protobuf ^6.0.0` 요구와 충돌한다.

해결 방법: `protobuf` constraint를 `^6.0.0`으로 올리고 `dart pub get` 후 agent tests를 실행한다.

수정 파일 및 체크리스트:

- [x] `../oto/pubspec.yaml` protobuf constraint 업데이트. (`^3.1.0` → `^6.0.0`)
- [x] `../oto/pubspec.lock`이 생성/변경되면 repo 정책에 맞춰 포함 여부 확인. (`dart pub get` 후 git 추적상 변경 없음)
- [x] (추가) constraint 상향으로 protobuf 3.x style generated 코드가 6.x와 충돌 → 원본 proto에서 protoc-gen-dart로 재생성. CODE_REVIEW `계획 대비 변경 사항` 참고.

테스트 작성: SKIP. dependency alignment는 기존 agent tests 실행 가능 여부로 검증한다.

중간 검증:

```bash
cd ../oto && dart pub get
```

예상 결과: exit code 0.

### [API-2] Persistent Edge Session API

문제: [edge_registration_client.dart](/config/workspace/oto/lib/oto/agent/edge_registration_client.dart:107)는 `register()` 하나만 제공하고, [edge_registration_client.dart](/config/workspace/oto/lib/oto/agent/edge_registration_client.dart:118)는 response 직후 close한다.

해결 방법: `EdgeAgentSession` 또는 `RegistrationSession` 타입을 추가한다. 새 API는 connect/register 후 client를 유지하고 `close()`를 노출한다. 기존 `register()`는 새 API를 사용하되 finally에서 close하는 one-shot wrapper로 보존한다.

Before:

```dart
// ../oto/lib/oto/agent/edge_registration_client.dart:107
Future<RegistrationResult> register(AgentConfig config) async {
  final endpoint = EdgeEndpoint.parse(config.edge.url);
  final socket = await ProtobufClient.connect(endpoint.host, endpoint.port);
  final client = _OtoIopClient(socket);
  try {
    final response =
        await client.sendRequest<iop.RegisterRequest, iop.RegisterResponse>(
      iop.RegisterRequest()..token = config.agent.enrollmentToken,
      timeout: const Duration(seconds: 5),
    );
    return RegistrationResult.fromResponse(response);
  } finally {
    await client.close();
  }
}
```

수정 파일 및 체크리스트:

- [x] `../oto/lib/oto/agent/edge_registration_client.dart` persistent session 타입 추가. (`EdgeAgentSession` + `openSession()`)
- [x] `../oto/lib/oto/agent/edge_registration_client.dart` one-shot `register()` 호환 보존.
- [x] `../oto/test/oto_agent_registration_test.dart` fake client 구조를 새 API와 호환되게 수정. (`FakeEdgeAgentSession` + `openSession()` override)

테스트 작성: `EdgeRegistrationClient` fake/session close test를 추가하거나 runner test에서 close 호출을 검증한다.

중간 검증:

```bash
cd ../oto && dart test test/oto_agent_registration_test.dart
```

예상 결과: exit code 0.

### [API-3] Runner Keeps Agent Alive

문제: [agent_runner.dart](/config/workspace/oto/lib/oto/agent/agent_runner.dart:40)는 registration success 로그 후 return한다. CLI help는 daemon이라고 말하지만 실제 연결은 유지되지 않는다.

해결 방법: `DefaultAgentRunner.run()`은 persistent session API를 사용하고, 성공 후 process signal/cancellation 경계까지 대기한다. 테스트 가능성을 위해 runner에 optional wait future 또는 stop signal injection을 둔다.

수정 파일 및 체크리스트:

- [x] `../oto/lib/oto/agent/agent_runner.dart` persistent session 사용. (`openSession()` + `finally` close)
- [x] `../oto/lib/oto/agent/agent_runner.dart` test injection 가능한 wait/stop hook 추가. (`waitForShutdown`, 기본값 SIGINT/SIGTERM 대기)
- [x] `../oto/test/oto_agent_registration_test.dart` success path가 session close까지 호출하는지 검증.
- [x] `../oto/lib/cli/commands/command_agent.dart` 변경이 필요하면 기존 `--config` parsing 유지. (call-site 조정 불필요로 미변경)

테스트 작성: fake client가 persistent session을 반환하고 runner wait future 완료 시 close되는지 확인한다.

중간 검증:

```bash
cd ../oto && dart test test/oto_agent_registration_test.dart
```

예상 결과: exit code 0.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `../oto/pubspec.yaml` | API-1 |
| `../oto/pubspec.lock` | API-1 |
| `../oto/lib/oto/agent/edge_registration_client.dart` | API-2 |
| `../oto/lib/oto/agent/agent_runner.dart` | API-3 |
| `../oto/lib/cli/commands/command_agent.dart` | API-3, only if runner call-site needs adjustment |
| `../oto/test/oto_agent_registration_test.dart` | API-2, API-3 |

## 최종 검증

```bash
cd ../oto && dart pub get
cd ../oto && dart analyze
cd ../oto && dart test test/oto_agent_registration_test.dart test/oto_agent_config_test.dart
```

예상 결과: 모두 exit code 0. Dart dependency solving output은 반드시 실제 출력으로 기록한다.

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