diff --git a/Makefile b/Makefile index e4d4f38..0502cfb 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,6 @@ -.PHONY: all setup test runner-get runner-analyze runner-test client-get client-analyze client-test core-test +.PHONY: all setup test runner-get runner-analyze runner-test client-get client-analyze client-test core-test proto proto-go proto-dart + +PROTOC_GEN_DART := $(shell command -v protoc-gen-dart 2>/dev/null) all: setup test @@ -25,4 +27,18 @@ client-test: core-test: cd services/core && go test ./... +proto: proto-go proto-dart + +proto-go: + mkdir -p services/core/oto + protoc -I proto --go_out=services/core --go_opt=paths=source_relative proto/oto/runner.proto + +proto-dart: +ifndef PROTOC_GEN_DART + $(error "protoc-gen-dart not found. Please install it to generate Dart protobuf files.") +endif + mkdir -p apps/runner/lib/oto/agent + protoc -I proto --dart_out=apps/runner/lib/oto/agent proto/oto/runner.proto + test: runner-test client-test core-test + diff --git a/agent-roadmap/ROADMAP.md b/agent-roadmap/ROADMAP.md index 0d45e79..6b22a34 100644 --- a/agent-roadmap/ROADMAP.md +++ b/agent-roadmap/ROADMAP.md @@ -17,7 +17,7 @@ OTO는 YAML 기반 빌드/배포 파이프라인을 실행하는 Dart CLI에서 - [폐기] Edge 직접 연결 기반 `oto-agent` - 경로: `agent-roadmap/archive/phase/edge-direct-oto-agent/PHASE.md` - 요약: OTO가 iop Edge와 proto-socket 기반 outbound 통신을 하는 경로는 검증 근거만 보존하고, 독립 OTO Control Plane 방향으로 전환하며 기본 제품 경로에서는 폐기한다. -- [계획] 독립 Control Plane 기반 OTO +- [진행중] 독립 Control Plane 기반 OTO - 경로: `agent-roadmap/phase/independent-control-plane/PHASE.md` - 요약: OTO를 iop 직접 연결 구조에서 분리해 `apps/runner`, `apps/client`, `services/core`를 가진 독립 CI/CD runner/control plane 제품 구조로 전환한다. - [계획] 메시지 기반 빌드 에이전트 diff --git a/agent-roadmap/phase/independent-control-plane/PHASE.md b/agent-roadmap/phase/independent-control-plane/PHASE.md index 6fa2127..46e9eef 100644 --- a/agent-roadmap/phase/independent-control-plane/PHASE.md +++ b/agent-roadmap/phase/independent-control-plane/PHASE.md @@ -2,7 +2,7 @@ ## 상태 -[계획] +[진행중] ## 목표 @@ -15,7 +15,7 @@ OTO를 iop Edge에 직접 붙는 domain agent가 아니라, 독립 실행 가능 완료, 검토중, 진행중, 계획, 스케치 순서로 두어 아래로 갈수록 미래 작업에 가까워지게 정렬한다. 스케치 Milestone은 아직 구현 가능한 계획이 아니므로 계획 Milestone보다 아래에 둔다. -- [계획] 독립 Control Plane 분리 마이그레이션 +- [진행중] 독립 Control Plane 분리 마이그레이션 - 경로: `agent-roadmap/phase/independent-control-plane/milestones/control-plane-separation-migration.md` - 요약: iop에 구현된 OTO 등록/heartbeat/bootstrap 구조를 OTO 전용 Server/Runner 계약으로 이관하고, repo를 `apps/runner`, `apps/client`, `services/core` monorepo로 전환한다. diff --git a/agent-roadmap/phase/independent-control-plane/milestones/control-plane-separation-migration.md b/agent-roadmap/phase/independent-control-plane/milestones/control-plane-separation-migration.md index f78cb83..aeed646 100644 --- a/agent-roadmap/phase/independent-control-plane/milestones/control-plane-separation-migration.md +++ b/agent-roadmap/phase/independent-control-plane/milestones/control-plane-separation-migration.md @@ -12,7 +12,7 @@ OTO repo를 독립 CI/CD 제품 monorepo로 전환하고, iop에 구현된 OTO ## 상태 -[계획] +[진행중] ## 승격 조건 @@ -37,19 +37,19 @@ OTO repo를 독립 CI/CD 제품 monorepo로 전환하고, iop에 구현된 OTO 현재 단일 Dart CLI repo를 OTO 제품 monorepo로 재배치하고, 초기 공유 패키지는 만들지 않는 최소 구조를 확정한다. -- [ ] [runner-app] 현재 OTO CLI/runtime 소스, 테스트, pubspec, 실행 entrypoint가 `apps/runner` 기준으로 이동되어 기존 로컬 CLI 실행이 유지된다. 검증: runner 경로에서 기존 Dart analyze/test가 통과한다. -- [ ] [client-app] Flutter client가 `apps/client` 기준으로 추가되고, OTO 독립 콘솔의 source of truth가 client app에 위치한다. 검증: client 경로에서 Flutter analyze/test 또는 초기 scaffold 검증이 통과한다. -- [ ] [core-service] OTO Server backend가 `services/core` 기준으로 생성되고, health/readiness와 local run entrypoint가 준비된다. 검증: core service 경로에서 Go test 또는 smoke가 통과한다. -- [ ] [root-entrypoints] root `bin/`, Makefile 또는 동등한 helper가 runner/client/core 작업을 위임하도록 정리되어 monorepo root에서 개발 진입점이 명확하다. +- [x] [runner-app] 현재 OTO CLI/runtime 소스, 테스트, pubspec, 실행 entrypoint가 `apps/runner` 기준으로 이동되어 기존 로컬 CLI 실행이 유지된다. 검증: `cd apps/runner && dart analyze` 통과, `dart test test/oto_cli_runtime_test.dart test/oto_catalog_cli_test.dart test/oto_validate_cli_test.dart test/oto_application_test.dart test/oto_context_test.dart test/oto_core_test.dart test/oto_system_runtime_test.dart test/oto_command_catalog_test.dart test/oto_command_runtime_test.dart test/oto_scheduler_runtime_test.dart test/oto_agent_config_test.dart test/oto_agent_cli_test.dart test/oto_agent_bootstrap_script_test.dart test/oto_agent_registration_test.dart` 통과. +- [x] [client-app] Flutter client가 `apps/client` 기준으로 추가되고, OTO 독립 콘솔의 source of truth가 client app에 위치한다. 검증: `cd apps/client && flutter analyze`, `flutter test` 통과. +- [x] [core-service] OTO Server backend가 `services/core` 기준으로 생성되고, health/readiness와 local run entrypoint가 준비된다. 검증: `cd services/core && go test ./...` 통과. +- [x] [root-entrypoints] root `Makefile`이 runner/client/core 작업을 위임하도록 정리되어 monorepo root에서 개발 진입점이 명확하다. ### Epic: [iop-extraction] iop OTO 연결 구조 추출 iop의 Edge 구조를 그대로 복제하지 않고, OTO에 필요한 등록/heartbeat/bootstrap 부분만 OTO Server 용어와 책임으로 이관한다. -- [ ] [source-map] iop/iop2oto의 OTO 관련 구현 위치가 migration source map으로 정리되어 있다. 포함: config `agent_kind=oto-agent`, bootstrap URL/release URL, `agent register`, transport register handler, first heartbeat online, domain agent summary, smoke target. -- [ ] [term-translation] iop 용어가 OTO 용어로 번역되어 있다. 예: Edge -> OTO Server, Node/DomainAgent -> Runner, domain agent summary -> runner status/capability, Edge bootstrap -> runner enrollment/bootstrap. -- [ ] [discard-iop-scope] OTO Server로 가져오지 않을 iop 범위가 명시되어 있다. 포함: model serving, OpenAI/A2A routing, generic node adapter, iop control-plane wire, iop client domain-agent panel. -- [ ] [migration-tests] 기존 OTO-iop online smoke가 OTO Server 대상 smoke로 전환될 수 있는 테스트 계획과 fixture 경계가 정리되어 있다. +- [x] [source-map] iop/iop2oto의 OTO 관련 구현 위치가 migration source map으로 정리되어 있다. 포함: config `agent_kind=oto-agent`, bootstrap URL/release URL, `agent register`, transport register handler, first heartbeat online, domain agent summary, smoke target. +- [x] [term-translation] iop 용어가 OTO 용어로 번역되어 있다. 예: Edge -> OTO Server, Node/DomainAgent -> Runner, domain agent summary -> runner status/capability, Edge bootstrap -> runner enrollment/bootstrap. +- [x] [discard-iop-scope] OTO Server로 가져오지 않을 iop 범위가 명시되어 있다. 포함: model serving, OpenAI/A2A routing, generic node adapter, iop control-plane wire, iop client domain-agent panel. +- [x] [migration-tests] 기존 OTO-iop online smoke가 OTO Server 대상 smoke로 전환될 수 있는 테스트 계획과 fixture 경계가 정리되어 있다. 검증: `cd apps/runner && dart test test/oto_agent_migration_plan_test.dart test/oto_agent_bootstrap_script_test.dart test/oto_agent_cli_test.dart test/oto_agent_config_test.dart test/oto_agent_registration_test.dart test/oto_iop_connection_smoke_test.dart` 통과. ### Epic: [server-runner-contract] Server-Runner 연결 계약 @@ -65,15 +65,15 @@ OTO Server와 OTO runner가 독립 제품 계약으로 등록, online, capabilit OTO, iop, NomadCode의 소비 관계를 분리해 OTO가 독립 CI/CD 제품으로 유지되도록 한다. - [ ] [oto-owns-cicd] OTO Server는 CI/CD runner registry, job, execution history, log/artifact 상태를 소유하고, iop Edge나 iop Control Plane을 전제로 하지 않는다. -- [ ] [iop-ai-provider] iop는 AI agent/model/provider 기능이 필요할 때만 선택적으로 호출하는 외부 provider로 정의되고, OTO runner 연결의 기본 경로에서 제외된다. -- [ ] [nomadcode-consumer] NomadCode는 OTO Server API를 소비하는 host 제품으로 정리되고, OTO client/server 구현을 native module로 복제하지 않는다. +- [x] [iop-ai-provider] iop는 AI agent/model/provider 기능이 필요할 때만 선택적으로 호출하는 외부 provider로 정의되고, OTO runner 연결의 기본 경로에서 제외된다. +- [x] [nomadcode-consumer] NomadCode는 OTO Server API를 소비하는 host 제품으로 정리되고, OTO client/server 구현을 native module로 복제하지 않는다. - [ ] [downstream-roadmap] 기존 `edge-direct-oto-agent`와 `message-based-build-agent` Phase/Milestone이 새 독립 OTO Server 방향에 맞게 후속 갱신 대상인지 정리된다. ## 완료 리뷰 - 상태: 없음 - 요청일: 없음 -- 완료 근거: 계획 상태이며 기능 Task와 검증이 아직 충족되지 않았다. +- 완료 근거: monorepo scaffold와 제품 경계 문서화 일부는 완료되었으나, OTO Server-Runner 계약과 iop smoke 전환은 아직 충족되지 않았다. - 리뷰 필요: - [ ] 사용자가 완료 결과를 확인했다 - [ ] archive 이동을 승인했다 @@ -100,3 +100,4 @@ OTO, iop, NomadCode의 소비 관계를 분리해 OTO가 독립 CI/CD 제품으 - 선행 작업: OTO-iop proto-socket 통신 기반, Edge bootstrap 계약, `oto-agent` 등록 흐름의 검증 근거 - 후속 작업: OTO Server job dispatch, log streaming, artifact reporting, typed command catalog remote execution, iop AI provider integration - 확인 필요: 기존 iop 직접 연결 Phase를 완료/보류/폐기 중 어떤 상태로 정리할지는 이 Milestone의 `downstream-roadmap` Task에서 판단한다. +- 동기화 근거(2026-06-05): `apps/runner`, `apps/client`, `services/core`, root `Makefile`이 존재하고 runner/client/core 기본 검증이 통과했다. `apps/runner/test/oto_agent_migration_plan_test.dart`가 legacy iop smoke와 future OTO Server smoke의 fixture 경계를 정리했고, `apps/runner/test/oto_iop_connection_smoke_test.dart`는 migration source evidence로 유지한다. 단, `apps/runner/lib/oto/agent/edge_registration_client.dart`, `apps/runner/lib/oto/agent/iop/runtime.pb.dart`, `apps/runner/test/oto_iop_connection_smoke_test.dart`가 아직 iop Edge 계약을 사용하므로 Server-Runner 계약 이관 Task는 미완료로 유지한다. diff --git a/agent-task/archive/2026/06/m-control-plane-separation-migration/01_wire_contract/code_review_cloud_G06_0.log b/agent-task/archive/2026/06/m-control-plane-separation-migration/01_wire_contract/code_review_cloud_G06_0.log new file mode 100644 index 0000000..919ca01 --- /dev/null +++ b/agent-task/archive/2026/06/m-control-plane-separation-migration/01_wire_contract/code_review_cloud_G06_0.log @@ -0,0 +1,155 @@ + + +# Code Review Reference - API + +> **[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. +> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation; record the needed decision in `사용자 리뷰 요청` and stop for code-review. +> 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-05 +task=m-control-plane-separation-migration/01_wire_contract, plan=0, tag=API + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/independent-control-plane/milestones/control-plane-separation-migration.md` +- Task ids: + - `wire-contract`: runner-server 장기 연결에 필요한 protobuf/proto-socket 또는 대체 wire 계약의 원본 위치와 생성물 경로가 확정된다. +- Completion mode: check-on-pass + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [API-1] Proto 원본 계약 추가 | [x] | +| [API-2] Generation target 추가 | [x] | +| [API-3] 생성물 compile 검증 | [x] | + +## 구현 체크리스트 + +- [x] [API-1] `proto/oto/runner.proto`를 만들고 Register/Heartbeat/Bootstrap 최소 메시지와 생성물 경로를 확정한다. +- [x] [API-2] root `Makefile`에 proto generation target을 추가해 Go/Dart 생성물을 같은 원본에서 갱신한다. +- [x] [API-3] 생성된 Go/Dart protobuf 파일을 추가하고 각 runtime이 import 가능한지 빌드 검증한다. 검증: `make proto`, `cd services/core && go test ./...`, `cd apps/runner && dart analyze` 통과. +- [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_cloud_G06_N.log`로 아카이브한다. +- [x] active `PLAN-*-G??.md`를 `plan_cloud_G06_M.log`로 아카이브한다. +- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다. +- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. +- [x] PASS이면 active task 디렉터리 `agent-task/m-control-plane-separation-migration/01_wire_contract/`를 `agent-task/archive/YYYY/MM/m-control-plane-separation-migration/01_wire_contract/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. +- [x] PASS이고 task group이 `m-`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다. +- [x] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-control-plane-separation-migration/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. +- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-cloud-G06.md`와 `CODE_REVIEW-cloud-G06.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로 이동한다. + +## 계획 대비 변경 사항 + +- 계획 대비 변경 사항 없음. 계획서에 기술된 내용에 맞추어 모든 구현을 완료함. + +## 주요 설계 결정 + +- OTO 전용 제어 평면 프로토콜 스펙(`proto/oto/runner.proto`)에 `RunnerCapability`, `CommandCatalogSummary`, `RegisterRunnerRequest`, `RegisterRunnerResponse`, `HeartbeatRequest`, `HeartbeatResponse`, `BootstrapCommandRequest`, `BootstrapCommandResponse` 메시지 구조를 정의하여 runner-server 통신 계약을 확보함. +- root `Makefile`에 `proto`, `proto-go`, `proto-dart` 타겟 및 `protoc-gen-dart` 감지 로직을 추가하여 단일 원본 proto 파일에서 Go 및 Dart 코드가 일관되게 생성 및 갱신되도록 빌드 자동화를 갖춤. + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- `proto/oto/runner.proto`가 OTO Server/Runner 용어만 사용한다. +- `make proto`가 같은 원본에서 Go/Dart 생성물을 만든다. +- generated files가 수동 편집 흔적 없이 compile된다. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +### API-1 중간 검증 +```bash +$ protoc -I proto --go_out=services/core --go_opt=paths=source_relative --dart_out=apps/runner/lib/oto/agent proto/oto/runner.proto +(정상 수행되어 아무런 에러 없이 종료됨) +``` + +### API-2 중간 검증 +```bash +$ make proto +mkdir -p services/core/oto +protoc -I proto --go_out=services/core --go_opt=paths=source_relative proto/oto/runner.proto +mkdir -p apps/runner/lib/oto/agent +protoc -I proto --dart_out=apps/runner/lib/oto/agent proto/oto/runner.proto +``` + +### API-3 중간 검증 +```bash +$ cd services/core && go test ./... +? github.com/toki/oto/services/core/cmd/oto-core [no test files] +ok github.com/toki/oto/services/core/internal/httpserver (cached) +? github.com/toki/oto/services/core/oto [no test files] +``` + +### 최종 검증 +```bash +$ make proto +mkdir -p services/core/oto +protoc -I proto --go_out=services/core --go_opt=paths=source_relative proto/oto/runner.proto +mkdir -p apps/runner/lib/oto/agent +protoc -I proto --dart_out=apps/runner/lib/oto/agent proto/oto/runner.proto + +$ cd services/core && go test ./... +? github.com/toki/oto/services/core/cmd/oto-core [no test files] +ok github.com/toki/oto/services/core/internal/httpserver (cached) +? github.com/toki/oto/services/core/oto [no test files] + +$ cd apps/runner && dart analyze +Analyzing runner... +No issues found! +``` + +--- + +> **[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 + - completeness: Pass + - test coverage: Pass + - API contract: Pass + - code quality: Pass + - plan deviation: Pass + - verification trust: Pass +- 발견된 문제: 없음 +- 다음 단계: PASS이므로 `complete.log`를 작성하고 active task 디렉터리를 archive로 이동한다. diff --git a/agent-task/archive/2026/06/m-control-plane-separation-migration/01_wire_contract/complete.log b/agent-task/archive/2026/06/m-control-plane-separation-migration/01_wire_contract/complete.log new file mode 100644 index 0000000..cd7825c --- /dev/null +++ b/agent-task/archive/2026/06/m-control-plane-separation-migration/01_wire_contract/complete.log @@ -0,0 +1,42 @@ +# Complete - m-control-plane-separation-migration/01_wire_contract + +## 완료 일시 + +2026-06-05 + +## 요약 + +OTO runner-server wire contract 원본 proto, Go/Dart 생성물 경로, 생성 Makefile target을 검토했고 1회차 리뷰에서 PASS로 종료했다. + +## 루프 이력 + +| Plan | Review | Verdict | 메모 | +|------|--------|---------|------| +| `plan_cloud_G06_0.log` | `code_review_cloud_G06_0.log` | PASS | proto 원본, generation target, Go/Dart compile 검증 모두 계획 범위와 일치함 | + +## 구현/정리 내용 + +- `proto/oto/runner.proto`에 Register/Heartbeat/Bootstrap 최소 메시지와 `oto.runner.v1` 패키지 계약을 추가했다. +- root `Makefile`에 `proto`, `proto-go`, `proto-dart` target을 추가해 같은 proto 원본에서 Go/Dart 생성물을 갱신하도록 했다. +- `services/core/oto/runner.pb.go`, `apps/runner/lib/oto/agent/oto/runner.pb.dart`, `.pbenum.dart`, `.pbjson.dart` 생성물과 Go protobuf dependency를 반영했다. + +## 최종 검증 + +- `make proto` - PASS; Go/Dart protobuf 생성 명령이 오류 없이 종료했다. +- `cd services/core && go test ./...` - PASS; `cmd/oto-core`, `internal/httpserver`, `oto` 패키지가 모두 통과 또는 no test files로 종료했다. +- `cd apps/runner && dart analyze` - PASS; `Analyzing runner... No issues found!` + +## Roadmap Completion + +- Milestone: `agent-roadmap/phase/independent-control-plane/milestones/control-plane-separation-migration.md` +- Completed task ids: + - `wire-contract`: PASS; evidence=`agent-task/archive/2026/06/m-control-plane-separation-migration/01_wire_contract/plan_cloud_G06_0.log`, `agent-task/archive/2026/06/m-control-plane-separation-migration/01_wire_contract/code_review_cloud_G06_0.log`; verification=`make proto`, `cd services/core && go test ./...`, `cd apps/runner && dart analyze` +- Not completed task ids: 없음 + +## 잔여 Nit + +- 없음 + +## 후속 작업 + +- 없음 diff --git a/agent-task/archive/2026/06/m-control-plane-separation-migration/01_wire_contract/plan_cloud_G06_0.log b/agent-task/archive/2026/06/m-control-plane-separation-migration/01_wire_contract/plan_cloud_G06_0.log new file mode 100644 index 0000000..2694715 --- /dev/null +++ b/agent-task/archive/2026/06/m-control-plane-separation-migration/01_wire_contract/plan_cloud_G06_0.log @@ -0,0 +1,215 @@ + + +# Plan - API + +## 이 파일을 읽는 구현 에이전트에게 + +`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채우는 것이 구현의 마지막 단계다. 구현 후 검증을 실행하고 실제 출력과 변경 내용을 review stub에 남긴 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 사용자 결정, 사용자 소유 외부 환경, scope 충돌로 막히면 review stub의 `사용자 리뷰 요청` 섹션에 근거를 채우고 멈춘다. 직접 사용자에게 묻거나 `USER_REVIEW.md`, `complete.log`, archive log를 만들지 않는다. + +## 배경 + +`server-runner-contract` 에픽은 Go server와 Dart runner가 같은 Server-Runner 계약을 공유해야 한다. 현재 runner는 iop runtime protobuf를 직접 import하고, core service는 health/readiness HTTP handler만 가진다. 먼저 원본 계약과 생성물 경로를 확정해야 등록, heartbeat, bootstrap 작업이 같은 메시지 이름과 필드를 기준으로 이어진다. + +## 사용자 리뷰 요청 흐름 + +구현 중 차단은 active `CODE_REVIEW-*-G??.md`의 `사용자 리뷰 요청` 섹션에 기록한다. 직접 사용자 프롬프트는 금지되며, code-review가 요청의 타당성을 검증하고 실제 `USER_REVIEW.md` 작성을 소유한다. + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/independent-control-plane/milestones/control-plane-separation-migration.md` +- Task ids: + - `wire-contract`: runner-server 장기 연결에 필요한 protobuf/proto-socket 또는 대체 wire 계약의 원본 위치와 생성물 경로가 확정된다. +- Completion mode: check-on-pass + +## 분석 결과 + +### 읽은 파일 + +- `agent-ops/rules/project/rules.md` +- `agent-ops/rules/common/rules-roadmap.md` +- `agent-ops/skills/common/router.md` +- `agent-ops/skills/common/plan/SKILL.md` +- `agent-roadmap/current.md` +- `agent-roadmap/phase/independent-control-plane/PHASE.md` +- `agent-roadmap/phase/independent-control-plane/milestones/control-plane-separation-migration.md` +- `agent-test/local/rules.md` +- `agent-test/local/agent-smoke.md` +- `agent-test/local/framework-smoke.md` +- `services/core/go.mod` +- `services/core/internal/httpserver/server.go` +- `services/core/internal/httpserver/server_test.go` +- `apps/runner/pubspec.yaml` +- `apps/runner/lib/oto/agent/edge_registration_client.dart` +- `apps/runner/test/oto_agent_registration_test.dart` +- `apps/runner/test/oto_agent_migration_plan_test.dart` +- `Makefile` + +### 테스트 환경 규칙 + +- `test_env=local`. +- `agent-test/local/rules.md`를 읽었고, agent/framework smoke profile을 적용한다. +- 적용 명령: runner `dart pub get`, `dart analyze`, agent test subset, framework 영향 시 `dart test`; core service는 agent-test profile이 없어 repository fallback `cd services/core && go test ./...`를 쓴다. +- 도구 확인: `protoc`, `protoc-gen-dart`, `protoc-gen-go`, `dart`, `go`가 PATH에 있다. +- `proto/oto/**`와 `services/core`용 agent-test profile은 아직 없으므로, 이번 계획은 fallback 검증을 명시한다. + +### 테스트 커버리지 공백 + +- shared proto 원본과 생성물 경로: 기존 테스트 없음. 새 Makefile target 또는 script dry-run 검증이 필요하다. +- Dart runner가 새 OTO proto import로 빌드 가능한지: 기존 iop registration tests만 있음. 새 generated import compile 검증이 필요하다. +- Go server가 새 generated package를 import 가능한지: 현재 health/readiness tests만 있음. 새 generated package compile 검증이 필요하다. + +### 심볼 참조 + +- 제거/rename 없음. 후속 plan에서 `EdgeRegistrationClient`, `EdgeEndpoint`, iop generated imports를 교체한다. + +### 분할 판단 + +- split decision policy를 먼저 평가했다. +- 공유 task group: `m-control-plane-separation-migration`. +- `01_wire_contract`: 독립 선행 작업. shared proto 원본과 generation target만 확정한다. +- `02+01_runner_registration`: `01`의 generated 계약 완료 후 runner/server 등록 구현. +- `03+02_online_state`: `02`의 registry와 registration endpoint 완료 후 heartbeat/timeout 구현. +- `04+02_bootstrap_command`: `02`의 token/runner registration model 완료 후 bootstrap command 구현. +- 이 plan은 선행 의존성이 없다. + +### 범위 결정 근거 + +- OTO job dispatch, log streaming, artifact reporting은 후속 Milestone 범위라 제외한다. +- iop generated protobuf 파일은 migration evidence라 직접 수정하지 않는다. +- `apps/client`는 생성된 계약 소비가 아직 없으므로 이번 작업에서 제외한다. +- proto-socket 장기 연결 runtime 구현은 등록/heartbeat HTTP fallback이 안정화된 뒤 별도 작업으로 둔다. + +### 빌드 등급 + +- `cloud-G06`: cross-language proto/schema와 생성 toolchain 경계가 있어 cloud lane이 적합하다. + +## 구현 체크리스트 + +- [ ] [API-1] `proto/oto/runner.proto`를 만들고 Register/Heartbeat/Bootstrap 최소 메시지와 생성물 경로를 확정한다. +- [ ] [API-2] root `Makefile`에 proto generation target을 추가해 Go/Dart 생성물을 같은 원본에서 갱신한다. +- [ ] [API-3] 생성된 Go/Dart protobuf 파일을 추가하고 각 runtime이 import 가능한지 빌드 검증한다. 검증: `make proto`, `cd services/core && go test ./...`, `cd apps/runner && dart analyze` 통과. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +### [API-1] Proto 원본 계약 추가 + +문제: [apps/runner/lib/oto/agent/edge_registration_client.dart](/config/workspace/oto/apps/runner/lib/oto/agent/edge_registration_client.dart:6)가 `iop/runtime.pb.dart`를 import하고, [services/core/internal/httpserver/server.go](/config/workspace/oto/services/core/internal/httpserver/server.go:15)는 OTO runner 계약을 전혀 등록하지 않는다. + +해결 방법: `proto/oto/runner.proto`를 추가한다. package는 `oto.runner.v1`, Go package는 `github.com/toki/oto/services/core/oto;otopb`로 둔다. 메시지는 `RunnerCapability`, `CommandCatalogSummary`, `RegisterRunnerRequest`, `RegisterRunnerResponse`, `HeartbeatRequest`, `HeartbeatResponse`, `BootstrapCommandRequest`, `BootstrapCommandResponse`만 둔다. + +Before: + +```text +1 module github.com/toki/oto/services/core +3 go 1.26 +``` + +After: + +```proto +syntax = "proto3"; + +package oto.runner.v1; + +option go_package = "github.com/toki/oto/services/core/oto;otopb"; + +message RunnerCapability { + string name = 1; + string version = 2; +} +``` + +수정 파일 및 체크리스트: + +- [ ] `proto/oto/runner.proto` 생성 +- [ ] message/field 이름은 runner/server 용어만 사용하고 Edge/Node/iop 용어를 쓰지 않는다. +- [ ] enrollment token, runner id, alias, protocol version, command catalog summary, heartbeat status, bootstrap command 필드를 포함한다. + +테스트 작성: proto 자체 단위 테스트는 작성하지 않는다. 생성물 compile 검증과 후속 등록/heartbeat tests가 사용 검증을 맡는다. + +중간 검증: + +```bash +protoc -I proto --go_out=services/core --go_opt=paths=source_relative --dart_out=apps/runner/lib/oto/agent oto/runner.proto +``` + +### [API-2] Generation target 추가 + +문제: [Makefile](/config/workspace/oto/Makefile:1)는 runner/client/core test target만 있고 proto generation target이 없다. + +해결 방법: `PROTOC_GEN_DART` 탐색과 `proto`, `proto-go`, `proto-dart` phony target을 추가한다. 출력 위치는 Go `services/core/oto/runner.pb.go`, Dart `apps/runner/lib/oto/agent/oto/runner.pb.dart` 계열로 고정한다. + +Before: + +```makefile +1 .PHONY: all setup test runner-get runner-analyze runner-test client-get client-analyze client-test core-test +25 core-test: +26 cd services/core && go test ./... +``` + +After: + +```makefile +.PHONY: proto proto-go proto-dart + +PROTOC_GEN_DART := $(shell command -v protoc-gen-dart 2>/dev/null) + +proto: proto-go proto-dart +``` + +수정 파일 및 체크리스트: + +- [ ] `Makefile` phony 목록에 proto targets 추가 +- [ ] `protoc-gen-dart`가 없으면 명확한 error를 출력한다. +- [ ] 생성 명령은 `proto/oto/runner.proto` 하나만 대상으로 둔다. + +테스트 작성: Makefile target은 별도 test file 없이 `make proto` 실행으로 검증한다. + +중간 검증: + +```bash +make proto +``` + +### [API-3] 생성물 compile 검증 + +문제: [apps/runner/pubspec.yaml](/config/workspace/oto/apps/runner/pubspec.yaml:23)는 `proto_socket`, [apps/runner/pubspec.yaml](/config/workspace/oto/apps/runner/pubspec.yaml:30)는 `protobuf`를 이미 갖고 있지만 OTO 계약 생성물이 없다. [services/core/go.mod](/config/workspace/oto/services/core/go.mod:1)는 generated Go protobuf dependency가 아직 없다. + +해결 방법: `make proto`로 생성물을 추가하고 Go module이 필요한 `google.golang.org/protobuf` dependency를 `go mod tidy`로 반영한다. + +수정 파일 및 체크리스트: + +- [ ] `services/core/oto/runner.pb.go` 추가 +- [ ] `apps/runner/lib/oto/agent/oto/runner.pb.dart`, `.pbenum.dart`, `.pbjson.dart` 추가 +- [ ] `services/core/go.mod`와 `go.sum` 변경이 생기면 함께 반영 +- [ ] generated files는 수동 편집하지 않는다. + +테스트 작성: 생성물은 수동 테스트를 작성하지 않는다. Go/Dart compile 검증으로 충분하다. + +중간 검증: + +```bash +cd services/core && go test ./... +``` + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `proto/oto/runner.proto` | API-1 | +| `Makefile` | API-2 | +| `services/core/oto/runner.pb.go` | API-3 | +| `apps/runner/lib/oto/agent/oto/runner.pb.dart` | API-3 | +| `apps/runner/lib/oto/agent/oto/runner.pbenum.dart` | API-3 | +| `apps/runner/lib/oto/agent/oto/runner.pbjson.dart` | API-3 | +| `services/core/go.mod` | API-3 | +| `services/core/go.sum` | API-3 | + +## 최종 검증 + +```bash +make proto +cd services/core && go test ./... +cd apps/runner && dart analyze +``` + +모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/agent-task/m-control-plane-separation-migration/02+01_runner_registration/CODE_REVIEW-cloud-G06.md b/agent-task/m-control-plane-separation-migration/02+01_runner_registration/CODE_REVIEW-cloud-G06.md new file mode 100644 index 0000000..3ef6de3 --- /dev/null +++ b/agent-task/m-control-plane-separation-migration/02+01_runner_registration/CODE_REVIEW-cloud-G06.md @@ -0,0 +1,119 @@ + + +# Code Review Reference - API + +> **[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`. +> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation. +> Finalization is review-agent-only. + +## 개요 + +date=2026-06-05 +task=m-control-plane-separation-migration/02+01_runner_registration, plan=0, tag=API + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/independent-control-plane/milestones/control-plane-separation-migration.md` +- Task ids: + - `runner-register`: runner 등록 요청/응답은 enrollment token뿐 아니라 runner id, alias, protocol version, command catalog/capability summary를 전달할 수 있게 정의된다. +- Completion mode: check-on-pass + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 종결 절차는 코드리뷰 에이전트 전용이다. + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [API-1] Predecessor 확인 | [ ] | +| [API-2] Core registration handler | [ ] | +| [API-3] Dart runner registration client | [ ] | +| [API-4] Loopback registration smoke | [ ] | + +## 구현 체크리스트 + +- [ ] [API-1] predecessor `01_wire_contract` 완료 근거를 확인한다. +- [ ] [API-2] services/core에 in-memory runner registry와 registration handler를 추가한다. +- [ ] [API-3] Dart runner registration client가 OTO Server 계약으로 runner id, alias, protocol version, catalog/capability summary를 보낸다. +- [ ] [API-4] Go/Dart tests를 추가하고 iop smoke는 migration evidence로 유지한다. 검증: `cd services/core && go test ./...`, `cd apps/runner && dart analyze`, agent test subset 통과. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. + +- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다. +- [ ] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G06_N.log`로 아카이브한다. +- [ ] active `PLAN-*-G??.md`를 `plan_cloud_G06_M.log`로 아카이브한다. +- [ ] `.gitignore`의 Agent-Ops 관리 block을 확인한다. +- [ ] PASS이면 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. +- [ ] PASS이면 active task 디렉터리를 archive로 이동한다. +- [ ] PASS이고 task group이 `m-`이면 완료 이벤트 메타데이터를 보고하고 roadmap을 직접 수정하지 않는다. +- [ ] PASS split 작업이면 빈 active parent를 제거하거나 유지 사유를 확인한다. +- [ ] WARN/FAIL이면 다음 active plan/review 또는 USER_REVIEW 흐름을 따른다. + +## 계획 대비 변경 사항 + +_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._ + +## 주요 설계 결정 + +_구현 에이전트가 주요 설계 결정 사항을 기록한다._ + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- predecessor `01_wire_contract` 완료 근거가 있다. +- registration handler가 token뿐 아니라 runner id, alias, protocol version, catalog/capabilities를 저장한다. +- Dart runner가 OTO Server 계약을 쓰며 iop smoke는 migration evidence로 남아 있다. + +## 검증 결과 + +### API-1 중간 검증 +```bash +$ find agent-task/m-control-plane-separation-migration -path '*/01_*/*' -name complete.log -print +(output) +``` + +### API-2 중간 검증 +```bash +$ cd services/core && go test ./... +(output) +``` + +### API-3 중간 검증 +```bash +$ cd apps/runner && dart test test/oto_agent_config_test.dart test/oto_agent_registration_test.dart test/oto_agent_cli_test.dart +(output) +``` + +### API-4 중간 검증 +```bash +$ cd apps/runner && dart test test/oto_agent_migration_plan_test.dart test/oto_agent_registration_test.dart +(output) +``` + +### 최종 검증 +```bash +$ cd services/core && go test ./... +$ cd apps/runner && dart analyze +$ cd apps/runner && dart test test/oto_agent_migration_plan_test.dart test/oto_agent_bootstrap_script_test.dart test/oto_agent_cli_test.dart test/oto_agent_config_test.dart test/oto_agent_registration_test.dart test/oto_iop_connection_smoke_test.dart +(output) +``` diff --git a/agent-task/m-control-plane-separation-migration/02+01_runner_registration/PLAN-cloud-G06.md b/agent-task/m-control-plane-separation-migration/02+01_runner_registration/PLAN-cloud-G06.md new file mode 100644 index 0000000..4eafe7e --- /dev/null +++ b/agent-task/m-control-plane-separation-migration/02+01_runner_registration/PLAN-cloud-G06.md @@ -0,0 +1,239 @@ + + +# Plan - API + +## 이 파일을 읽는 구현 에이전트에게 + +`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채우는 것이 구현의 마지막 단계다. 구현 후 검증을 실행하고 실제 출력과 변경 내용을 review stub에 남긴 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 사용자 결정, 사용자 소유 외부 환경, scope 충돌로 막히면 review stub의 `사용자 리뷰 요청` 섹션에 근거를 채우고 멈춘다. 직접 사용자에게 묻거나 `USER_REVIEW.md`, `complete.log`, archive log를 만들지 않는다. + +## 배경 + +runner 등록은 현재 iop Edge token만 보내고 OTO Server registry에 아무 상태도 남기지 않는다. Server-Runner 계약은 enrollment token과 함께 runner id, alias, protocol version, command catalog summary를 전달해야 한다. `01_wire_contract`가 완료된 뒤 generated OTO proto를 사용해 Go server와 Dart runner의 등록 표면을 연결한다. + +## 사용자 리뷰 요청 흐름 + +구현 중 차단은 active `CODE_REVIEW-*-G??.md`의 `사용자 리뷰 요청` 섹션에 기록한다. 직접 사용자 프롬프트는 금지되며, code-review가 요청의 타당성을 검증하고 실제 `USER_REVIEW.md` 작성을 소유한다. + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/independent-control-plane/milestones/control-plane-separation-migration.md` +- Task ids: + - `runner-register`: runner 등록 요청/응답은 enrollment token뿐 아니라 runner id, alias, protocol version, command catalog/capability summary를 전달할 수 있게 정의된다. +- Completion mode: check-on-pass + +## 분석 결과 + +### 읽은 파일 + +- `agent-test/local/rules.md` +- `agent-test/local/agent-smoke.md` +- `agent-ops/rules/project/domain/agent/rules.md` +- `services/core/internal/httpserver/server.go` +- `services/core/internal/httpserver/server_test.go` +- `services/core/cmd/oto-core/main.go` +- `services/core/go.mod` +- `apps/runner/lib/oto/agent/agent_config.dart` +- `apps/runner/lib/oto/agent/agent_runner.dart` +- `apps/runner/lib/oto/agent/edge_registration_client.dart` +- `apps/runner/lib/cli/commands/command_agent.dart` +- `apps/runner/test/oto_agent_config_test.dart` +- `apps/runner/test/oto_agent_registration_test.dart` +- `apps/runner/test/oto_agent_cli_test.dart` +- `apps/runner/test/oto_agent_migration_plan_test.dart` +- `apps/runner/pubspec.yaml` + +### 테스트 환경 규칙 + +- `test_env=local`. +- agent profile 적용: `cd apps/runner && dart analyze`, `cd apps/runner && dart test test/oto_agent_migration_plan_test.dart test/oto_agent_bootstrap_script_test.dart test/oto_agent_cli_test.dart test/oto_agent_config_test.dart test/oto_agent_registration_test.dart test/oto_iop_connection_smoke_test.dart`. +- core service는 profile이 없어 fallback `cd services/core && go test ./...`를 사용한다. +- `02+01`은 predecessor `01`의 `complete.log`가 필요하다. 작성 시점에는 `01_wire_contract` active plan/review만 있고 `complete.log`가 없으므로, 구현 시작 전 완료 근거를 다시 확인해야 한다. + +### 테스트 커버리지 공백 + +- OTO Server registration handler: 기존 테스트 없음. Go handler/registry tests 추가 필요. +- Dart runner registration request payload: 기존 test는 token 전달만 확인한다. protocol version/catalog/capabilities assertion 추가 필요. +- Loopback OTO Server registration smoke: migration plan test가 future boundary만 담고 실제 fixture는 없음. 이번 작업에서 focused integration test를 추가한다. + +### 심볼 참조 + +- `EdgeRegistrationClient`: `apps/runner/lib/oto/agent/agent_runner.dart`, `apps/runner/test/oto_agent_registration_test.dart`, `apps/runner/test/oto_iop_connection_smoke_test.dart`. +- `EdgeEndpoint`: `apps/runner/test/oto_agent_registration_test.dart`. +- `EdgeConnectionConfig`: `apps/runner/lib/oto/agent/agent_config.dart`, `apps/runner/test/oto_agent_config_test.dart`, `apps/runner/test/oto_agent_registration_test.dart`, `apps/runner/test/oto_agent_cli_test.dart`, `apps/runner/test/oto_iop_connection_smoke_test.dart`. +- Rename는 이번 작업에서 공개 config 호환을 깨지 않도록 optional alias class 또는 compatibility parser를 유지한다. + +### 분할 판단 + +- split decision policy를 평가했고, API foundation과 registration rollout 경계가 있어 분할한다. +- 이 plan은 `02+01_runner_registration`이며 predecessor `01_wire_contract` 완료가 필요하다. +- 작성 시점 predecessor 상태: missing complete log. `agent-task/m-control-plane-separation-migration/01_wire_contract/`에는 active plan/review만 있다. +- 구현 전 `agent-task/m-control-plane-separation-migration/01_wire_contract/complete.log` 또는 archive의 matching `01_*` complete log를 확인한다. + +### 범위 결정 근거 + +- heartbeat timeout과 disconnect 상태는 `03+02_online_state`로 분리한다. +- bootstrap command endpoint는 `04+02_bootstrap_command`로 분리한다. +- iop smoke는 migration evidence로 유지하고 이번 작업에서 삭제하지 않는다. +- `apps/client`는 아직 등록 API 소비자가 아니므로 제외한다. + +### 빌드 등급 + +- `cloud-G06`: protocol payload와 Go/Dart call-site migration이 함께 있어 cloud lane이 적합하다. + +## 구현 체크리스트 + +- [ ] [API-1] predecessor `01_wire_contract` 완료 근거를 확인한다. +- [ ] [API-2] services/core에 in-memory runner registry와 registration handler를 추가한다. +- [ ] [API-3] Dart runner registration client가 OTO Server 계약으로 runner id, alias, protocol version, catalog/capability summary를 보낸다. +- [ ] [API-4] Go/Dart tests를 추가하고 iop smoke는 migration evidence로 유지한다. 검증: `cd services/core && go test ./...`, `cd apps/runner && dart analyze`, agent test subset 통과. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 의존 관계 및 구현 순서 + +- `02+01_runner_registration`는 directory name 기준으로 `01_wire_contract`에 의존한다. +- 구현 시작 전 `agent-task/m-control-plane-separation-migration/01_wire_contract/complete.log` 또는 archive matching `01_*` complete log를 확인한다. + +### [API-1] Predecessor 확인 + +문제: 이 작업은 generated OTO proto를 import해야 하므로 `01_wire_contract` 없이 구현하면 파일 경로와 message 이름을 추정하게 된다. + +해결 방법: 구현 전 predecessor complete log를 확인하고, 없으면 review stub의 사용자 리뷰 요청이 아니라 작업 미충족으로 중단한다. + +수정 파일 및 체크리스트: + +- [ ] active predecessor complete log 확인 +- [ ] 없으면 archive matching candidate 확인 + +테스트 작성: 없음. + +중간 검증: + +```bash +find agent-task/m-control-plane-separation-migration -path '*/01_*/*' -name complete.log -print +``` + +### [API-2] Core registration handler + +문제: [services/core/internal/httpserver/server.go](/config/workspace/oto/services/core/internal/httpserver/server.go:15)는 `/healthz`, `/readyz`만 등록하고 runner registration surface가 없다. + +해결 방법: `internal/runnerregistry` 패키지와 `/api/v1/runners/register` handler를 추가한다. handler는 enrollment token, runner id, alias, protocol version, command catalog summary, capabilities를 받아 accepted response와 server-assigned bootstrap/status hints를 반환한다. + +Before: + +```go +15 func NewServer(addr string) *Server { +16 mux := http.NewServeMux() +19 mux.HandleFunc("/healthz", handleHealthz) +20 mux.HandleFunc("/readyz", handleReadyz) +``` + +After: + +```go +registry := runnerregistry.New() +mux.HandleFunc("/api/v1/runners/register", handleRunnerRegister(registry)) +``` + +수정 파일 및 체크리스트: + +- [ ] `services/core/internal/runnerregistry/registry.go` +- [ ] `services/core/internal/runnerregistry/registry_test.go` +- [ ] `services/core/internal/httpserver/server.go` +- [ ] `services/core/internal/httpserver/server_test.go` + +테스트 작성: Go tests 추가. accepted registration, missing token reject, registry stores alias/protocol/catalog/capabilities. + +중간 검증: + +```bash +cd services/core && go test ./... +``` + +### [API-3] Dart runner registration client + +문제: [apps/runner/lib/oto/agent/edge_registration_client.dart](/config/workspace/oto/apps/runner/lib/oto/agent/edge_registration_client.dart:143)는 `iop.RegisterRequest()..token`만 보낸다. [apps/runner/lib/oto/agent/agent_runner.dart](/config/workspace/oto/apps/runner/lib/oto/agent/agent_runner.dart:34)는 user-facing log도 OTO Edge를 말한다. + +해결 방법: `OtoServerRegistrationClient`를 추가하거나 기존 class를 compatibility wrapper로 유지하면서 내부 구현을 OTO Server contract로 교체한다. `AgentConfig`에는 `server` section을 새 표준으로 추가하고, 기존 `edge` section은 migration compatibility로 읽는다. + +Before: + +```dart +final response = + await client.sendRequest( + iop.RegisterRequest()..token = config.agent.enrollmentToken, + timeout: const Duration(seconds: 5), +); +``` + +After: + +```dart +final request = RegisterRunnerRequest() + ..enrollmentToken = config.agent.enrollmentToken + ..runnerId = config.agent.id + ..alias = config.agent.alias ?? '' + ..protocolVersion = 'oto.runner.v1'; +``` + +수정 파일 및 체크리스트: + +- [ ] `apps/runner/lib/oto/agent/agent_config.dart` +- [ ] `apps/runner/lib/oto/agent/agent_runner.dart` +- [ ] `apps/runner/lib/oto/agent/edge_registration_client.dart` 또는 새 `oto_server_registration_client.dart` +- [ ] `apps/runner/test/oto_agent_config_test.dart` +- [ ] `apps/runner/test/oto_agent_registration_test.dart` +- [ ] `apps/runner/test/oto_agent_cli_test.dart` + +테스트 작성: Dart tests 수정/추가. server config parsing, payload includes runner id/alias/protocol/catalog, rejected response closes session. + +중간 검증: + +```bash +cd apps/runner && dart test test/oto_agent_config_test.dart test/oto_agent_registration_test.dart test/oto_agent_cli_test.dart +``` + +### [API-4] Loopback registration smoke + +문제: [apps/runner/test/oto_agent_migration_plan_test.dart](/config/workspace/oto/apps/runner/test/oto_agent_migration_plan_test.dart:56)는 target fixture를 future boundary로만 정의한다. + +해결 방법: services/core를 dynamic port로 시작하거나 `httptest` equivalent endpoint를 Dart test fixture로 제공해 runner registration accepted evidence를 수집한다. 외부 iop checkout 없이 실행되어야 한다. + +수정 파일 및 체크리스트: + +- [ ] `apps/runner/test/oto_agent_migration_plan_test.dart` 또는 새 focused test +- [ ] `agent-test/local/agent-smoke.md` 명령이 새 test를 포함하는지 확인 + +테스트 작성: Dart focused integration test 추가. iop smoke 삭제는 제외. + +중간 검증: + +```bash +cd apps/runner && dart test test/oto_agent_migration_plan_test.dart test/oto_agent_registration_test.dart +``` + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `services/core/internal/runnerregistry/registry.go` | API-2 | +| `services/core/internal/runnerregistry/registry_test.go` | API-2 | +| `services/core/internal/httpserver/server.go` | API-2 | +| `services/core/internal/httpserver/server_test.go` | API-2 | +| `apps/runner/lib/oto/agent/agent_config.dart` | API-3 | +| `apps/runner/lib/oto/agent/agent_runner.dart` | API-3 | +| `apps/runner/lib/oto/agent/edge_registration_client.dart` | API-3 | +| `apps/runner/test/oto_agent_config_test.dart` | API-3 | +| `apps/runner/test/oto_agent_registration_test.dart` | API-3 | +| `apps/runner/test/oto_agent_cli_test.dart` | API-3 | +| `apps/runner/test/oto_agent_migration_plan_test.dart` | API-4 | +| `agent-test/local/agent-smoke.md` | API-4 | + +## 최종 검증 + +```bash +cd services/core && go test ./... +cd apps/runner && dart analyze +cd apps/runner && dart test test/oto_agent_migration_plan_test.dart test/oto_agent_bootstrap_script_test.dart test/oto_agent_cli_test.dart test/oto_agent_config_test.dart test/oto_agent_registration_test.dart test/oto_iop_connection_smoke_test.dart +``` + +모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/agent-task/m-control-plane-separation-migration/03+02_online_state/CODE_REVIEW-cloud-G06.md b/agent-task/m-control-plane-separation-migration/03+02_online_state/CODE_REVIEW-cloud-G06.md new file mode 100644 index 0000000..fc1fef5 --- /dev/null +++ b/agent-task/m-control-plane-separation-migration/03+02_online_state/CODE_REVIEW-cloud-G06.md @@ -0,0 +1,117 @@ + + +# Code Review Reference - API + +> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.** +> Fill implementation-owned sections, paste verification output, and stop with active files in place. + +## 개요 + +date=2026-06-05 +task=m-control-plane-separation-migration/03+02_online_state, plan=0, tag=API + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/independent-control-plane/milestones/control-plane-separation-migration.md` +- Task ids: + - `online-state`: registration accepted와 first heartbeat online 상태가 OTO Server registry에 분리되어 기록되고, heartbeat timeout/disconnect 실패 상태가 정의된다. +- Completion mode: check-on-pass + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 종결 절차는 코드리뷰 에이전트 전용이다. + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [API-1] Predecessor 확인 | [ ] | +| [API-2] Registry state machine | [ ] | +| [API-3] Heartbeat handler | [ ] | +| [API-4] Runner heartbeat emission | [ ] | +| [API-5] OTO Server online smoke | [ ] | + +## 구현 체크리스트 + +- [ ] [API-1] predecessor `02+01_runner_registration` 완료 근거를 확인한다. +- [ ] [API-2] registry에 accepted/online/disconnected/heartbeat_timeout 상태와 clock-injected timeout 판정을 추가한다. +- [ ] [API-3] `/api/v1/runners/{id}/heartbeat` 또는 plan 01 proto에 맞는 heartbeat handler를 추가한다. +- [ ] [API-4] Dart runner가 registration accepted 후 first heartbeat를 보내고 shutdown/disconnect를 정리한다. +- [ ] [API-5] Go/Dart tests로 accepted와 online, timeout/disconnect 상태를 검증한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. + +- [ ] 판정을 append한다. +- [ ] active plan/review를 log로 아카이브한다. +- [ ] PASS이면 complete.log 작성 후 task directory를 archive로 이동한다. +- [ ] PASS이고 m-prefixed task group이면 완료 이벤트 메타데이터만 보고한다. +- [ ] WARN/FAIL이면 다음 loop 상태를 만든다. + +## 계획 대비 변경 사항 + +_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._ + +## 주요 설계 결정 + +_구현 에이전트가 주요 설계 결정 사항을 기록한다._ + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- accepted와 online 상태가 분리된다. +- timeout/disconnect가 deterministic clock test로 검증된다. +- runner heartbeat loop가 shutdown 시 정리된다. + +## 검증 결과 + +### API-1 중간 검증 +```bash +$ find agent-task/m-control-plane-separation-migration -path '*/02*/*' -name complete.log -print +(output) +``` + +### API-2 중간 검증 +```bash +$ cd services/core && go test ./internal/runnerregistry +(output) +``` + +### API-3 중간 검증 +```bash +$ cd services/core && go test ./internal/httpserver +(output) +``` + +### API-4 중간 검증 +```bash +$ cd apps/runner && dart test test/oto_agent_registration_test.dart +(output) +``` + +### API-5 중간 검증 +```bash +$ cd apps/runner && dart test test/oto_agent_migration_plan_test.dart test/oto_agent_registration_test.dart +(output) +``` + +### 최종 검증 +```bash +$ cd services/core && go test ./... +$ cd apps/runner && dart analyze +$ cd apps/runner && dart test test/oto_agent_migration_plan_test.dart test/oto_agent_bootstrap_script_test.dart test/oto_agent_cli_test.dart test/oto_agent_config_test.dart test/oto_agent_registration_test.dart test/oto_iop_connection_smoke_test.dart +(output) +``` diff --git a/agent-task/m-control-plane-separation-migration/03+02_online_state/PLAN-cloud-G06.md b/agent-task/m-control-plane-separation-migration/03+02_online_state/PLAN-cloud-G06.md new file mode 100644 index 0000000..751d933 --- /dev/null +++ b/agent-task/m-control-plane-separation-migration/03+02_online_state/PLAN-cloud-G06.md @@ -0,0 +1,202 @@ + + +# Plan - API + +## 이 파일을 읽는 구현 에이전트에게 + +`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채우는 것이 구현의 마지막 단계다. 검증 출력까지 채운 뒤 active 파일을 그대로 두고 리뷰를 요청한다. 사용자 결정이나 외부 환경으로 막히면 review stub의 `사용자 리뷰 요청`만 채우고 멈춘다. + +## 배경 + +현재 legacy smoke는 iop Edge log의 `node.online` marker를 기다린다. OTO Server는 runner registry를 소유해야 하므로 registration accepted와 first heartbeat online, timeout/disconnect 상태를 분리해 기록해야 한다. 이 작업은 `02+01_runner_registration`의 registry와 endpoint 위에 heartbeat state machine을 추가한다. + +## 사용자 리뷰 요청 흐름 + +구현 중 차단은 active review stub에 기록한다. 직접 사용자 질문, `USER_REVIEW.md`, archive, `complete.log` 작성은 금지된다. + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/independent-control-plane/milestones/control-plane-separation-migration.md` +- Task ids: + - `online-state`: registration accepted와 first heartbeat online 상태가 OTO Server registry에 분리되어 기록되고, heartbeat timeout/disconnect 실패 상태가 정의된다. +- Completion mode: check-on-pass + +## 분석 결과 + +### 읽은 파일 + +- `services/core/internal/httpserver/server.go` +- `services/core/internal/httpserver/server_test.go` +- `apps/runner/lib/oto/agent/agent_runner.dart` +- `apps/runner/lib/oto/agent/edge_registration_client.dart` +- `apps/runner/test/oto_agent_registration_test.dart` +- `apps/runner/test/oto_iop_connection_smoke_test.dart` +- `apps/runner/test/oto_agent_migration_plan_test.dart` +- `agent-test/local/rules.md` +- `agent-test/local/agent-smoke.md` + +### 테스트 환경 규칙 + +- `test_env=local`. +- agent profile 적용, core service는 fallback `cd services/core && go test ./...`. +- `03+02`는 predecessor `02`의 complete log가 필요하다. 작성 시점에는 `02+01_runner_registration` active plan/review만 있고 `complete.log`가 없으므로, 구현 시작 전 완료 근거를 다시 확인해야 한다. + +### 테스트 커버리지 공백 + +- first heartbeat online transition: 기존 OTO Server test 없음. +- heartbeat timeout/disconnect: 기존 test 없음. +- Dart runner heartbeat emission: 현재 proto-socket library heartbeat에 의존하고 runner-owned heartbeat payload 없음. + +### 심볼 참조 + +- `openSession`: `apps/runner/lib/oto/agent/agent_runner.dart`, `apps/runner/test/oto_agent_registration_test.dart`, `apps/runner/test/oto_iop_connection_smoke_test.dart`. +- `runtimeConfig`: registration tests와 iop smoke에서 사용한다. + +### 분할 판단 + +- registration endpoint와 registry가 먼저 필요하므로 `03+02_online_state`로 분리했다. +- predecessor index `02`는 `02+01_runner_registration` 완료를 의미한다. +- 작성 시점 predecessor 상태: missing complete log. `agent-task/m-control-plane-separation-migration/02+01_runner_registration/`에는 active plan/review만 있다. + +### 범위 결정 근거 + +- online state는 in-memory registry와 local test fixture까지만 구현한다. +- 운영용 durable storage, audit log, distributed runner timeout은 제외한다. +- job dispatch keepalive protocol은 제외한다. + +### 빌드 등급 + +- `cloud-G06`: time-based state, disconnect semantics, runner/server protocol이 맞물린다. + +## 구현 체크리스트 + +- [ ] [API-1] predecessor `02+01_runner_registration` 완료 근거를 확인한다. +- [ ] [API-2] registry에 accepted/online/disconnected/heartbeat_timeout 상태와 clock-injected timeout 판정을 추가한다. +- [ ] [API-3] `/api/v1/runners/{id}/heartbeat` 또는 plan 01 proto에 맞는 heartbeat handler를 추가한다. +- [ ] [API-4] Dart runner가 registration accepted 후 first heartbeat를 보내고 shutdown/disconnect를 정리한다. +- [ ] [API-5] Go/Dart tests로 accepted와 online, timeout/disconnect 상태를 검증한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 의존 관계 및 구현 순서 + +- `03+02_online_state`는 `02+01_runner_registration`에 의존한다. +- 구현 전 `02_*` 또는 `02+*` matching `complete.log`를 확인한다. + +### [API-1] Predecessor 확인 + +문제: heartbeat는 registration registry와 runner id가 있어야 의미가 있다. + +해결 방법: `02+01_runner_registration` complete log를 확인한다. + +수정 파일 및 체크리스트: + +- [ ] active/archive predecessor complete log 확인 + +테스트 작성: 없음. + +중간 검증: + +```bash +find agent-task/m-control-plane-separation-migration -path '*/02*/*' -name complete.log -print +``` + +### [API-2] Registry state machine + +문제: [services/core/internal/httpserver/server.go](/config/workspace/oto/services/core/internal/httpserver/server.go:19)는 registry state endpoint가 없다. [apps/runner/test/oto_iop_connection_smoke_test.dart](/config/workspace/oto/apps/runner/test/oto_iop_connection_smoke_test.dart:121)는 iop Edge log marker에 의존한다. + +해결 방법: registry record에 `accepted_at`, `first_heartbeat_at`, `last_heartbeat_at`, `status`, `failure_reason`을 둔다. status는 `accepted`, `online`, `disconnected`, `heartbeat_timeout`만 먼저 정의한다. + +수정 파일 및 체크리스트: + +- [ ] `services/core/internal/runnerregistry/registry.go` +- [ ] `services/core/internal/runnerregistry/registry_test.go` + +테스트 작성: Go state-machine tests. clock injection으로 timeout을 deterministic하게 검증한다. + +중간 검증: + +```bash +cd services/core && go test ./internal/runnerregistry +``` + +### [API-3] Heartbeat handler + +문제: Server mux가 runner heartbeat를 받지 않는다. + +해결 방법: registration handler와 같은 registry를 공유하는 heartbeat endpoint를 추가한다. unknown runner는 404 또는 rejected response로 처리하고, first heartbeat는 status를 online으로 전환한다. + +수정 파일 및 체크리스트: + +- [ ] `services/core/internal/httpserver/server.go` +- [ ] `services/core/internal/httpserver/server_test.go` + +테스트 작성: handler tests. registration 후 first heartbeat online, unknown runner, method not allowed. + +중간 검증: + +```bash +cd services/core && go test ./internal/httpserver +``` + +### [API-4] Runner heartbeat emission + +문제: [apps/runner/lib/oto/agent/agent_runner.dart](/config/workspace/oto/apps/runner/lib/oto/agent/agent_runner.dart:59)는 accepted 후 shutdown signal만 기다린다. + +해결 방법: OTO Server session에 heartbeat loop를 추가한다. 테스트에서는 interval을 주입해 즉시 first heartbeat를 검증하고 shutdown 시 loop를 정리한다. + +수정 파일 및 체크리스트: + +- [ ] `apps/runner/lib/oto/agent/agent_runner.dart` +- [ ] `apps/runner/lib/oto/agent/edge_registration_client.dart` 또는 새 server client +- [ ] `apps/runner/test/oto_agent_registration_test.dart` + +테스트 작성: Dart tests. first heartbeat sent before shutdown wait, session close cancels heartbeat loop. + +중간 검증: + +```bash +cd apps/runner && dart test test/oto_agent_registration_test.dart +``` + +### [API-5] OTO Server online smoke + +문제: migration target의 expected evidence는 [apps/runner/test/oto_agent_migration_plan_test.dart](/config/workspace/oto/apps/runner/test/oto_agent_migration_plan_test.dart:66)에 문자열로만 있다. + +해결 방법: loopback OTO Server fixture에서 registration accepted와 first heartbeat online evidence를 실제 assertion으로 만든다. + +수정 파일 및 체크리스트: + +- [ ] `apps/runner/test/oto_agent_migration_plan_test.dart` 또는 새 `oto_server_connection_smoke_test.dart` +- [ ] `agent-test/local/agent-smoke.md` + +테스트 작성: focused Dart smoke 추가. 외부 iop repo 없이 실행되어야 한다. + +중간 검증: + +```bash +cd apps/runner && dart test test/oto_agent_migration_plan_test.dart test/oto_agent_registration_test.dart +``` + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `services/core/internal/runnerregistry/registry.go` | API-2 | +| `services/core/internal/runnerregistry/registry_test.go` | API-2 | +| `services/core/internal/httpserver/server.go` | API-3 | +| `services/core/internal/httpserver/server_test.go` | API-3 | +| `apps/runner/lib/oto/agent/agent_runner.dart` | API-4 | +| `apps/runner/lib/oto/agent/edge_registration_client.dart` | API-4 | +| `apps/runner/test/oto_agent_registration_test.dart` | API-4 | +| `apps/runner/test/oto_agent_migration_plan_test.dart` | API-5 | +| `agent-test/local/agent-smoke.md` | API-5 | + +## 최종 검증 + +```bash +cd services/core && go test ./... +cd apps/runner && dart analyze +cd apps/runner && dart test test/oto_agent_migration_plan_test.dart test/oto_agent_bootstrap_script_test.dart test/oto_agent_cli_test.dart test/oto_agent_config_test.dart test/oto_agent_registration_test.dart test/oto_iop_connection_smoke_test.dart +``` + +모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/agent-task/m-control-plane-separation-migration/04+02_bootstrap_command/CODE_REVIEW-cloud-G07.md b/agent-task/m-control-plane-separation-migration/04+02_bootstrap_command/CODE_REVIEW-cloud-G07.md new file mode 100644 index 0000000..4552718 --- /dev/null +++ b/agent-task/m-control-plane-separation-migration/04+02_bootstrap_command/CODE_REVIEW-cloud-G07.md @@ -0,0 +1,119 @@ + + +# Code Review Reference - API + +> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.** +> Fill implementation-owned sections, paste verification output, and stop with active files in place. + +## 개요 + +date=2026-06-05 +task=m-control-plane-separation-migration/04+02_bootstrap_command, plan=0, tag=API + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/independent-control-plane/milestones/control-plane-separation-migration.md` +- Task ids: + - `bootstrap-command`: OTO Server가 runner bootstrap command를 발급하고, bootstrap script가 `apps/runner` binary/config를 기준으로 runner를 실행하는 흐름이 정의된다. +- Completion mode: check-on-pass + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 종결 절차는 코드리뷰 에이전트 전용이다. + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [API-1] Predecessor 확인 | [ ] | +| [API-2] Server bootstrap command endpoint | [ ] | +| [API-3] Bootstrap script server terminology | [ ] | +| [API-4] Config parser와 CLI help | [ ] | +| [API-5] Full bootstrap smoke | [ ] | + +## 구현 체크리스트 + +- [ ] [API-1] predecessor `02+01_runner_registration` 완료 근거를 확인한다. +- [ ] [API-2] services/core에 runner bootstrap command response를 추가한다. +- [ ] [API-3] bootstrap script가 `--server-url` 표준 flag와 `server:` config section을 쓰도록 갱신하되 `--edge-url` compatibility를 유지하거나 명시적으로 deprecate한다. +- [ ] [API-4] Dart config parser와 CLI help가 OTO Server 용어를 우선 사용한다. +- [ ] [API-5] Go/Dart script tests를 추가하고 token 노출 방지와 PID path 회귀를 유지한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. + +- [ ] 판정을 append한다. +- [ ] active plan/review를 log로 아카이브한다. +- [ ] PASS이면 complete.log 작성 후 task directory를 archive로 이동한다. +- [ ] PASS이고 m-prefixed task group이면 완료 이벤트 메타데이터만 보고한다. +- [ ] WARN/FAIL이면 다음 loop 상태를 만든다. + +## 계획 대비 변경 사항 + +_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._ + +## 주요 설계 결정 + +_구현 에이전트가 주요 설계 결정 사항을 기록한다._ + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- server-issued bootstrap command와 shell flag 이름이 일치한다. +- `--server-url`이 표준이고 `--edge-url` 처리 방침이 테스트로 고정된다. +- enrollment token이 stdout/stderr에 노출되지 않는다. +- PID path 회귀가 유지된다. + +## 검증 결과 + +### API-1 중간 검증 +```bash +$ find agent-task/m-control-plane-separation-migration -path '*/02*/*' -name complete.log -print +(output) +``` + +### API-2 중간 검증 +```bash +$ cd services/core && go test ./internal/httpserver +(output) +``` + +### API-3 중간 검증 +```bash +$ cd apps/runner && dart test test/oto_agent_bootstrap_script_test.dart +(output) +``` + +### API-4 중간 검증 +```bash +$ cd apps/runner && dart test test/oto_agent_config_test.dart test/oto_agent_cli_test.dart +(output) +``` + +### API-5 중간 검증 +```bash +$ cd services/core && go test ./internal/httpserver +$ cd apps/runner && dart test test/oto_agent_bootstrap_script_test.dart +(output) +``` + +### 최종 검증 +```bash +$ cd services/core && go test ./... +$ cd apps/runner && dart analyze +$ cd apps/runner && dart test test/oto_agent_migration_plan_test.dart test/oto_agent_bootstrap_script_test.dart test/oto_agent_cli_test.dart test/oto_agent_config_test.dart test/oto_agent_registration_test.dart test/oto_iop_connection_smoke_test.dart +(output) +``` diff --git a/agent-task/m-control-plane-separation-migration/04+02_bootstrap_command/PLAN-cloud-G07.md b/agent-task/m-control-plane-separation-migration/04+02_bootstrap_command/PLAN-cloud-G07.md new file mode 100644 index 0000000..f83051f --- /dev/null +++ b/agent-task/m-control-plane-separation-migration/04+02_bootstrap_command/PLAN-cloud-G07.md @@ -0,0 +1,228 @@ + + +# Plan - API + +## 이 파일을 읽는 구현 에이전트에게 + +`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채우는 것이 구현의 마지막 단계다. shell/bootstrap 검증 출력은 실제 stdout/stderr를 붙이고, active 파일을 그대로 둔 채 리뷰를 요청한다. 사용자 결정이나 외부 release artifact가 필요해 막히면 review stub의 `사용자 리뷰 요청` 섹션만 채운다. + +## 배경 + +현재 bootstrap script는 `--edge-url`을 받아 `edge:` config를 생성한다. OTO Server가 runner bootstrap command를 발급하고, script가 `apps/runner` binary/config 기준으로 실행하려면 server 용어와 config shape를 등록 계약에 맞춰야 한다. shell entrypoint와 server-issued command가 맞물리므로 registration 작업 이후에 분리 실행한다. + +## 사용자 리뷰 요청 흐름 + +구현 중 차단은 active review stub에 기록한다. 직접 사용자 질문, `USER_REVIEW.md`, archive, `complete.log` 작성은 금지된다. + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/independent-control-plane/milestones/control-plane-separation-migration.md` +- Task ids: + - `bootstrap-command`: OTO Server가 runner bootstrap command를 발급하고, bootstrap script가 `apps/runner` binary/config를 기준으로 runner를 실행하는 흐름이 정의된다. +- Completion mode: check-on-pass + +## 분석 결과 + +### 읽은 파일 + +- `apps/runner/assets/script/shell/oto_agent_bootstrap.sh` +- `apps/runner/test/oto_agent_bootstrap_script_test.dart` +- `apps/runner/lib/oto/agent/agent_config.dart` +- `apps/runner/lib/cli/commands/command_agent.dart` +- `services/core/internal/httpserver/server.go` +- `services/core/internal/httpserver/server_test.go` +- `agent-test/local/rules.md` +- `agent-test/local/agent-smoke.md` + +### 테스트 환경 규칙 + +- `test_env=local`. +- agent profile 적용: script 변경 후 `cd apps/runner && dart analyze`, agent test subset. +- core service는 fallback `cd services/core && go test ./...`. +- `04+02`는 predecessor `02+01_runner_registration` 완료가 필요하다. 작성 시점에는 `02+01_runner_registration` active plan/review만 있고 `complete.log`가 없으므로, 구현 시작 전 완료 근거를 다시 확인해야 한다. + +### 테스트 커버리지 공백 + +- Server-issued bootstrap command endpoint: 기존 Go tests 없음. +- Script `--server-url` compatibility: 기존 tests는 `--edge-url`만 본다. +- Config `server:` section generation: 기존 tests는 `edge:` keys만 본다. + +### 심볼 참조 + +- `--edge-url`: `apps/runner/assets/script/shell/oto_agent_bootstrap.sh`, `apps/runner/test/oto_agent_bootstrap_script_test.dart`. +- `edge:` config: `apps/runner/lib/oto/agent/agent_config.dart`, `apps/runner/test/oto_agent_config_test.dart`, bootstrap script/test. + +### 분할 판단 + +- shell/bootstrap orchestration은 registration model이 선행되어야 하므로 `04+02_bootstrap_command`로 분리한다. +- 작성 시점 predecessor 상태: missing complete log. `agent-task/m-control-plane-separation-migration/02+01_runner_registration/`에는 active plan/review만 있다. +- terminal/script 검증이 중심이라 별도 plan으로 둔다. + +### 범위 결정 근거 + +- 실제 release artifact publishing, checksum signing, package hosting은 제외한다. +- background process manager나 service install 정책은 기존 script 수준만 유지한다. +- iop bootstrap command는 migration source로만 남기고 복제하지 않는다. + +### 빌드 등급 + +- `cloud-G07`: shell/bootstrap command generation과 process execution 검증이 중심이라 cloud lane 고등급이 적합하다. + +## 구현 체크리스트 + +- [ ] [API-1] predecessor `02+01_runner_registration` 완료 근거를 확인한다. +- [ ] [API-2] services/core에 runner bootstrap command response를 추가한다. +- [ ] [API-3] bootstrap script가 `--server-url` 표준 flag와 `server:` config section을 쓰도록 갱신하되 `--edge-url` compatibility를 유지하거나 명시적으로 deprecate한다. +- [ ] [API-4] Dart config parser와 CLI help가 OTO Server 용어를 우선 사용한다. +- [ ] [API-5] Go/Dart script tests를 추가하고 token 노출 방지와 PID path 회귀를 유지한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 의존 관계 및 구현 순서 + +- `04+02_bootstrap_command`는 `02+01_runner_registration`에 의존한다. +- 구현 전 `02_*` 또는 `02+*` matching `complete.log`를 확인한다. + +### [API-1] Predecessor 확인 + +문제: bootstrap command에는 enrollment token과 server URL, runner id/alias config shape가 필요하다. + +해결 방법: registration plan complete log를 확인한다. + +수정 파일 및 체크리스트: + +- [ ] active/archive predecessor complete log 확인 + +테스트 작성: 없음. + +중간 검증: + +```bash +find agent-task/m-control-plane-separation-migration -path '*/02*/*' -name complete.log -print +``` + +### [API-2] Server bootstrap command endpoint + +문제: [services/core/internal/httpserver/server.go](/config/workspace/oto/services/core/internal/httpserver/server.go:19)는 bootstrap command endpoint가 없다. + +해결 방법: `/api/v1/runners/bootstrap-command` 또는 plan 01 proto에 맞는 endpoint를 추가한다. response는 `curl -fsSL /bootstrap/oto-agent.sh | bash -s -- --server-url ... --agent-id ... --enrollment-token ...` 형태의 command와 script URL을 포함한다. + +수정 파일 및 체크리스트: + +- [ ] `services/core/internal/httpserver/server.go` +- [ ] `services/core/internal/httpserver/server_test.go` +- [ ] 필요 시 `services/core/internal/runnerregistry/registry.go` + +테스트 작성: Go handler tests. command contains server URL and token placeholder, rejects missing runner id/token. + +중간 검증: + +```bash +cd services/core && go test ./internal/httpserver +``` + +### [API-3] Bootstrap script server terminology + +문제: [apps/runner/assets/script/shell/oto_agent_bootstrap.sh](/config/workspace/oto/apps/runner/assets/script/shell/oto_agent_bootstrap.sh:16)는 `--edge-url`, [apps/runner/assets/script/shell/oto_agent_bootstrap.sh](/config/workspace/oto/apps/runner/assets/script/shell/oto_agent_bootstrap.sh:132)는 `edge:` config를 생성한다. + +해결 방법: `--server-url`을 표준 flag로 추가하고 config는 `server: url:`을 우선 작성한다. migration compatibility가 필요하면 `--edge-url`은 alias로 받아 같은 변수에 매핑한다. + +Before: + +```bash +16 echo "Usage: $0 --edge-url --agent-id --enrollment-token --release-base-url [options]" +28 --edge-url) +132 edge: +133 url: "$edge_url" +``` + +After: + +```bash +echo "Usage: $0 --server-url --agent-id --enrollment-token --release-base-url [options]" +--server-url|--edge-url) + server_url="$2" +server: + url: "$server_url" +``` + +수정 파일 및 체크리스트: + +- [ ] `apps/runner/assets/script/shell/oto_agent_bootstrap.sh` +- [ ] `apps/runner/test/oto_agent_bootstrap_script_test.dart` + +테스트 작성: Dart script contract/functional tests 갱신. `--server-url` required, `--edge-url` alias 여부, token non-leak, PID path 유지. + +중간 검증: + +```bash +cd apps/runner && dart test test/oto_agent_bootstrap_script_test.dart +``` + +### [API-4] Config parser와 CLI help + +문제: [apps/runner/lib/oto/agent/agent_config.dart](/config/workspace/oto/apps/runner/lib/oto/agent/agent_config.dart:125)는 `edge` section만 읽고, [apps/runner/lib/cli/commands/command_agent.dart](/config/workspace/oto/apps/runner/lib/cli/commands/command_agent.dart:96)는 Edge server를 말한다. + +해결 방법: `server` section을 우선 읽고 기존 `edge` section은 migration fallback으로 지원한다. CLI/help/log는 OTO Server로 바꾼다. + +수정 파일 및 체크리스트: + +- [ ] `apps/runner/lib/oto/agent/agent_config.dart` +- [ ] `apps/runner/lib/cli/commands/command_agent.dart` +- [ ] `apps/runner/lib/oto/agent/agent_runner.dart` +- [ ] `apps/runner/test/oto_agent_config_test.dart` +- [ ] `apps/runner/test/oto_agent_cli_test.dart` + +테스트 작성: config parser tests에 `server:` 표준, `edge:` fallback, missing server/edge error를 추가한다. + +중간 검증: + +```bash +cd apps/runner && dart test test/oto_agent_config_test.dart test/oto_agent_cli_test.dart +``` + +### [API-5] Full bootstrap smoke + +문제: [apps/runner/test/oto_agent_bootstrap_script_test.dart](/config/workspace/oto/apps/runner/test/oto_agent_bootstrap_script_test.dart:77)는 script archive/PID behavior는 검증하지만 server-issued command와 연결하지 않는다. + +해결 방법: Go endpoint response command와 shell script contract가 같은 flag 이름을 쓰는지 검증한다. 외부 release artifact 없이 fake curl/tar fixture를 유지한다. + +수정 파일 및 체크리스트: + +- [ ] `services/core/internal/httpserver/server_test.go` +- [ ] `apps/runner/test/oto_agent_bootstrap_script_test.dart` +- [ ] `agent-test/local/agent-smoke.md` + +테스트 작성: Go command rendering test와 Dart script functional test를 둘 다 추가한다. + +중간 검증: + +```bash +cd services/core && go test ./internal/httpserver +cd apps/runner && dart test test/oto_agent_bootstrap_script_test.dart +``` + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `services/core/internal/httpserver/server.go` | API-2 | +| `services/core/internal/httpserver/server_test.go` | API-2, API-5 | +| `services/core/internal/runnerregistry/registry.go` | API-2 | +| `apps/runner/assets/script/shell/oto_agent_bootstrap.sh` | API-3 | +| `apps/runner/test/oto_agent_bootstrap_script_test.dart` | API-3, API-5 | +| `apps/runner/lib/oto/agent/agent_config.dart` | API-4 | +| `apps/runner/lib/cli/commands/command_agent.dart` | API-4 | +| `apps/runner/lib/oto/agent/agent_runner.dart` | API-4 | +| `apps/runner/test/oto_agent_config_test.dart` | API-4 | +| `apps/runner/test/oto_agent_cli_test.dart` | API-4 | +| `agent-test/local/agent-smoke.md` | API-5 | + +## 최종 검증 + +```bash +cd services/core && go test ./... +cd apps/runner && dart analyze +cd apps/runner && dart test test/oto_agent_migration_plan_test.dart test/oto_agent_bootstrap_script_test.dart test/oto_agent_cli_test.dart test/oto_agent_config_test.dart test/oto_agent_registration_test.dart test/oto_iop_connection_smoke_test.dart +``` + +모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/agent-test/local/agent-smoke.md b/agent-test/local/agent-smoke.md index 8458a07..3f0ed92 100644 --- a/agent-test/local/agent-smoke.md +++ b/agent-test/local/agent-smoke.md @@ -37,7 +37,7 @@ last_rule_updated_at: 2026-06-05 - setup: `dart pub get` - lint: `dart analyze` -- unit: `dart test test/oto_agent_bootstrap_script_test.dart test/oto_agent_cli_test.dart test/oto_agent_config_test.dart test/oto_agent_registration_test.dart test/oto_iop_connection_smoke_test.dart` +- unit: `dart test test/oto_agent_migration_plan_test.dart test/oto_agent_bootstrap_script_test.dart test/oto_agent_cli_test.dart test/oto_agent_config_test.dart test/oto_agent_registration_test.dart test/oto_iop_connection_smoke_test.dart` - smoke: - e2e: - model: diff --git a/apps/runner/lib/oto/agent/oto/runner.pb.dart b/apps/runner/lib/oto/agent/oto/runner.pb.dart new file mode 100644 index 0000000..a924d64 --- /dev/null +++ b/apps/runner/lib/oto/agent/oto/runner.pb.dart @@ -0,0 +1,596 @@ +// This is a generated file - do not edit. +// +// Generated from oto/runner.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports + +import 'dart:core' as $core; + +import 'package:protobuf/protobuf.dart' as $pb; + +import 'runner.pbenum.dart'; + +export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions; + +export 'runner.pbenum.dart'; + +class RunnerCapability extends $pb.GeneratedMessage { + factory RunnerCapability({ + $core.String? name, + $core.String? version, + }) { + final result = create(); + if (name != null) result.name = name; + if (version != null) result.version = version; + return result; + } + + RunnerCapability._(); + + factory RunnerCapability.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory RunnerCapability.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'RunnerCapability', + package: const $pb.PackageName(_omitMessageNames ? '' : 'oto.runner.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'name') + ..aOS(2, _omitFieldNames ? '' : 'version') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + RunnerCapability clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + RunnerCapability copyWith(void Function(RunnerCapability) updates) => + super.copyWith((message) => updates(message as RunnerCapability)) + as RunnerCapability; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static RunnerCapability create() => RunnerCapability._(); + @$core.override + RunnerCapability createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static RunnerCapability getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static RunnerCapability? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get name => $_getSZ(0); + @$pb.TagNumber(1) + set name($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasName() => $_has(0); + @$pb.TagNumber(1) + void clearName() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get version => $_getSZ(1); + @$pb.TagNumber(2) + set version($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasVersion() => $_has(1); + @$pb.TagNumber(2) + void clearVersion() => $_clearField(2); +} + +class CommandCatalogSummary extends $pb.GeneratedMessage { + factory CommandCatalogSummary({ + $core.Iterable<$core.String>? commandTypes, + }) { + final result = create(); + if (commandTypes != null) result.commandTypes.addAll(commandTypes); + return result; + } + + CommandCatalogSummary._(); + + factory CommandCatalogSummary.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory CommandCatalogSummary.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'CommandCatalogSummary', + package: const $pb.PackageName(_omitMessageNames ? '' : 'oto.runner.v1'), + createEmptyInstance: create) + ..pPS(1, _omitFieldNames ? '' : 'commandTypes') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + CommandCatalogSummary clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + CommandCatalogSummary copyWith( + void Function(CommandCatalogSummary) updates) => + super.copyWith((message) => updates(message as CommandCatalogSummary)) + as CommandCatalogSummary; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static CommandCatalogSummary create() => CommandCatalogSummary._(); + @$core.override + CommandCatalogSummary createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static CommandCatalogSummary getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static CommandCatalogSummary? _defaultInstance; + + @$pb.TagNumber(1) + $pb.PbList<$core.String> get commandTypes => $_getList(0); +} + +class RegisterRunnerRequest extends $pb.GeneratedMessage { + factory RegisterRunnerRequest({ + $core.String? enrollmentToken, + $core.String? runnerId, + $core.String? alias, + $core.String? protocolVersion, + RunnerCapability? capability, + CommandCatalogSummary? commandCatalog, + }) { + final result = create(); + if (enrollmentToken != null) result.enrollmentToken = enrollmentToken; + if (runnerId != null) result.runnerId = runnerId; + if (alias != null) result.alias = alias; + if (protocolVersion != null) result.protocolVersion = protocolVersion; + if (capability != null) result.capability = capability; + if (commandCatalog != null) result.commandCatalog = commandCatalog; + return result; + } + + RegisterRunnerRequest._(); + + factory RegisterRunnerRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory RegisterRunnerRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'RegisterRunnerRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'oto.runner.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'enrollmentToken') + ..aOS(2, _omitFieldNames ? '' : 'runnerId') + ..aOS(3, _omitFieldNames ? '' : 'alias') + ..aOS(4, _omitFieldNames ? '' : 'protocolVersion') + ..aOM(5, _omitFieldNames ? '' : 'capability', + subBuilder: RunnerCapability.create) + ..aOM(6, _omitFieldNames ? '' : 'commandCatalog', + subBuilder: CommandCatalogSummary.create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + RegisterRunnerRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + RegisterRunnerRequest copyWith( + void Function(RegisterRunnerRequest) updates) => + super.copyWith((message) => updates(message as RegisterRunnerRequest)) + as RegisterRunnerRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static RegisterRunnerRequest create() => RegisterRunnerRequest._(); + @$core.override + RegisterRunnerRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static RegisterRunnerRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static RegisterRunnerRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get enrollmentToken => $_getSZ(0); + @$pb.TagNumber(1) + set enrollmentToken($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasEnrollmentToken() => $_has(0); + @$pb.TagNumber(1) + void clearEnrollmentToken() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get runnerId => $_getSZ(1); + @$pb.TagNumber(2) + set runnerId($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasRunnerId() => $_has(1); + @$pb.TagNumber(2) + void clearRunnerId() => $_clearField(2); + + @$pb.TagNumber(3) + $core.String get alias => $_getSZ(2); + @$pb.TagNumber(3) + set alias($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasAlias() => $_has(2); + @$pb.TagNumber(3) + void clearAlias() => $_clearField(3); + + @$pb.TagNumber(4) + $core.String get protocolVersion => $_getSZ(3); + @$pb.TagNumber(4) + set protocolVersion($core.String value) => $_setString(3, value); + @$pb.TagNumber(4) + $core.bool hasProtocolVersion() => $_has(3); + @$pb.TagNumber(4) + void clearProtocolVersion() => $_clearField(4); + + @$pb.TagNumber(5) + RunnerCapability get capability => $_getN(4); + @$pb.TagNumber(5) + set capability(RunnerCapability value) => $_setField(5, value); + @$pb.TagNumber(5) + $core.bool hasCapability() => $_has(4); + @$pb.TagNumber(5) + void clearCapability() => $_clearField(5); + @$pb.TagNumber(5) + RunnerCapability ensureCapability() => $_ensure(4); + + @$pb.TagNumber(6) + CommandCatalogSummary get commandCatalog => $_getN(5); + @$pb.TagNumber(6) + set commandCatalog(CommandCatalogSummary value) => $_setField(6, value); + @$pb.TagNumber(6) + $core.bool hasCommandCatalog() => $_has(5); + @$pb.TagNumber(6) + void clearCommandCatalog() => $_clearField(6); + @$pb.TagNumber(6) + CommandCatalogSummary ensureCommandCatalog() => $_ensure(5); +} + +class RegisterRunnerResponse extends $pb.GeneratedMessage { + factory RegisterRunnerResponse({ + $core.bool? accepted, + $core.String? rejectReason, + $core.String? runnerId, + $core.String? alias, + }) { + final result = create(); + if (accepted != null) result.accepted = accepted; + if (rejectReason != null) result.rejectReason = rejectReason; + if (runnerId != null) result.runnerId = runnerId; + if (alias != null) result.alias = alias; + return result; + } + + RegisterRunnerResponse._(); + + factory RegisterRunnerResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory RegisterRunnerResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'RegisterRunnerResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'oto.runner.v1'), + createEmptyInstance: create) + ..aOB(1, _omitFieldNames ? '' : 'accepted') + ..aOS(2, _omitFieldNames ? '' : 'rejectReason') + ..aOS(3, _omitFieldNames ? '' : 'runnerId') + ..aOS(4, _omitFieldNames ? '' : 'alias') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + RegisterRunnerResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + RegisterRunnerResponse copyWith( + void Function(RegisterRunnerResponse) updates) => + super.copyWith((message) => updates(message as RegisterRunnerResponse)) + as RegisterRunnerResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static RegisterRunnerResponse create() => RegisterRunnerResponse._(); + @$core.override + RegisterRunnerResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static RegisterRunnerResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static RegisterRunnerResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.bool get accepted => $_getBF(0); + @$pb.TagNumber(1) + set accepted($core.bool value) => $_setBool(0, value); + @$pb.TagNumber(1) + $core.bool hasAccepted() => $_has(0); + @$pb.TagNumber(1) + void clearAccepted() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get rejectReason => $_getSZ(1); + @$pb.TagNumber(2) + set rejectReason($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasRejectReason() => $_has(1); + @$pb.TagNumber(2) + void clearRejectReason() => $_clearField(2); + + @$pb.TagNumber(3) + $core.String get runnerId => $_getSZ(2); + @$pb.TagNumber(3) + set runnerId($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasRunnerId() => $_has(2); + @$pb.TagNumber(3) + void clearRunnerId() => $_clearField(3); + + @$pb.TagNumber(4) + $core.String get alias => $_getSZ(3); + @$pb.TagNumber(4) + set alias($core.String value) => $_setString(3, value); + @$pb.TagNumber(4) + $core.bool hasAlias() => $_has(3); + @$pb.TagNumber(4) + void clearAlias() => $_clearField(4); +} + +class HeartbeatRequest extends $pb.GeneratedMessage { + factory HeartbeatRequest({ + $core.String? runnerId, + HeartbeatStatus? status, + }) { + final result = create(); + if (runnerId != null) result.runnerId = runnerId; + if (status != null) result.status = status; + return result; + } + + HeartbeatRequest._(); + + factory HeartbeatRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory HeartbeatRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'HeartbeatRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'oto.runner.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'runnerId') + ..aE(2, _omitFieldNames ? '' : 'status', + enumValues: HeartbeatStatus.values) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + HeartbeatRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + HeartbeatRequest copyWith(void Function(HeartbeatRequest) updates) => + super.copyWith((message) => updates(message as HeartbeatRequest)) + as HeartbeatRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static HeartbeatRequest create() => HeartbeatRequest._(); + @$core.override + HeartbeatRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static HeartbeatRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static HeartbeatRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get runnerId => $_getSZ(0); + @$pb.TagNumber(1) + set runnerId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasRunnerId() => $_has(0); + @$pb.TagNumber(1) + void clearRunnerId() => $_clearField(1); + + @$pb.TagNumber(2) + HeartbeatStatus get status => $_getN(1); + @$pb.TagNumber(2) + set status(HeartbeatStatus value) => $_setField(2, value); + @$pb.TagNumber(2) + $core.bool hasStatus() => $_has(1); + @$pb.TagNumber(2) + void clearStatus() => $_clearField(2); +} + +class HeartbeatResponse extends $pb.GeneratedMessage { + factory HeartbeatResponse({ + $core.bool? success, + $core.String? errorMessage, + }) { + final result = create(); + if (success != null) result.success = success; + if (errorMessage != null) result.errorMessage = errorMessage; + return result; + } + + HeartbeatResponse._(); + + factory HeartbeatResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory HeartbeatResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'HeartbeatResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'oto.runner.v1'), + createEmptyInstance: create) + ..aOB(1, _omitFieldNames ? '' : 'success') + ..aOS(2, _omitFieldNames ? '' : 'errorMessage') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + HeartbeatResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + HeartbeatResponse copyWith(void Function(HeartbeatResponse) updates) => + super.copyWith((message) => updates(message as HeartbeatResponse)) + as HeartbeatResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static HeartbeatResponse create() => HeartbeatResponse._(); + @$core.override + HeartbeatResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static HeartbeatResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static HeartbeatResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.bool get success => $_getBF(0); + @$pb.TagNumber(1) + set success($core.bool value) => $_setBool(0, value); + @$pb.TagNumber(1) + $core.bool hasSuccess() => $_has(0); + @$pb.TagNumber(1) + void clearSuccess() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get errorMessage => $_getSZ(1); + @$pb.TagNumber(2) + set errorMessage($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasErrorMessage() => $_has(1); + @$pb.TagNumber(2) + void clearErrorMessage() => $_clearField(2); +} + +class BootstrapCommandRequest extends $pb.GeneratedMessage { + factory BootstrapCommandRequest({ + $core.String? runnerId, + }) { + final result = create(); + if (runnerId != null) result.runnerId = runnerId; + return result; + } + + BootstrapCommandRequest._(); + + factory BootstrapCommandRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory BootstrapCommandRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'BootstrapCommandRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'oto.runner.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'runnerId') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + BootstrapCommandRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + BootstrapCommandRequest copyWith( + void Function(BootstrapCommandRequest) updates) => + super.copyWith((message) => updates(message as BootstrapCommandRequest)) + as BootstrapCommandRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static BootstrapCommandRequest create() => BootstrapCommandRequest._(); + @$core.override + BootstrapCommandRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static BootstrapCommandRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static BootstrapCommandRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get runnerId => $_getSZ(0); + @$pb.TagNumber(1) + set runnerId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasRunnerId() => $_has(0); + @$pb.TagNumber(1) + void clearRunnerId() => $_clearField(1); +} + +class BootstrapCommandResponse extends $pb.GeneratedMessage { + factory BootstrapCommandResponse({ + $core.String? bootstrapCommand, + }) { + final result = create(); + if (bootstrapCommand != null) result.bootstrapCommand = bootstrapCommand; + return result; + } + + BootstrapCommandResponse._(); + + factory BootstrapCommandResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory BootstrapCommandResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'BootstrapCommandResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'oto.runner.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'bootstrapCommand') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + BootstrapCommandResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + BootstrapCommandResponse copyWith( + void Function(BootstrapCommandResponse) updates) => + super.copyWith((message) => updates(message as BootstrapCommandResponse)) + as BootstrapCommandResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static BootstrapCommandResponse create() => BootstrapCommandResponse._(); + @$core.override + BootstrapCommandResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static BootstrapCommandResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static BootstrapCommandResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get bootstrapCommand => $_getSZ(0); + @$pb.TagNumber(1) + set bootstrapCommand($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasBootstrapCommand() => $_has(0); + @$pb.TagNumber(1) + void clearBootstrapCommand() => $_clearField(1); +} + +const $core.bool _omitFieldNames = + $core.bool.fromEnvironment('protobuf.omit_field_names'); +const $core.bool _omitMessageNames = + $core.bool.fromEnvironment('protobuf.omit_message_names'); diff --git a/apps/runner/lib/oto/agent/oto/runner.pbenum.dart b/apps/runner/lib/oto/agent/oto/runner.pbenum.dart new file mode 100644 index 0000000..f40f3ac --- /dev/null +++ b/apps/runner/lib/oto/agent/oto/runner.pbenum.dart @@ -0,0 +1,40 @@ +// This is a generated file - do not edit. +// +// Generated from oto/runner.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports + +import 'dart:core' as $core; + +import 'package:protobuf/protobuf.dart' as $pb; + +class HeartbeatStatus extends $pb.ProtobufEnum { + static const HeartbeatStatus HEARTBEAT_STATUS_UNSPECIFIED = HeartbeatStatus._( + 0, _omitEnumNames ? '' : 'HEARTBEAT_STATUS_UNSPECIFIED'); + static const HeartbeatStatus HEARTBEAT_STATUS_HEALTHY = + HeartbeatStatus._(1, _omitEnumNames ? '' : 'HEARTBEAT_STATUS_HEALTHY'); + static const HeartbeatStatus HEARTBEAT_STATUS_UNHEALTHY = + HeartbeatStatus._(2, _omitEnumNames ? '' : 'HEARTBEAT_STATUS_UNHEALTHY'); + + static const $core.List values = [ + HEARTBEAT_STATUS_UNSPECIFIED, + HEARTBEAT_STATUS_HEALTHY, + HEARTBEAT_STATUS_UNHEALTHY, + ]; + + static final $core.List _byValue = + $pb.ProtobufEnum.$_initByValueList(values, 2); + static HeartbeatStatus? valueOf($core.int value) => + value < 0 || value >= _byValue.length ? null : _byValue[value]; + + const HeartbeatStatus._(super.value, super.name); +} + +const $core.bool _omitEnumNames = + $core.bool.fromEnvironment('protobuf.omit_enum_names'); diff --git a/apps/runner/lib/oto/agent/oto/runner.pbjson.dart b/apps/runner/lib/oto/agent/oto/runner.pbjson.dart new file mode 100644 index 0000000..ccc3d2b --- /dev/null +++ b/apps/runner/lib/oto/agent/oto/runner.pbjson.dart @@ -0,0 +1,180 @@ +// This is a generated file - do not edit. +// +// Generated from oto/runner.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports +// ignore_for_file: unused_import + +import 'dart:convert' as $convert; +import 'dart:core' as $core; +import 'dart:typed_data' as $typed_data; + +@$core.Deprecated('Use heartbeatStatusDescriptor instead') +const HeartbeatStatus$json = { + '1': 'HeartbeatStatus', + '2': [ + {'1': 'HEARTBEAT_STATUS_UNSPECIFIED', '2': 0}, + {'1': 'HEARTBEAT_STATUS_HEALTHY', '2': 1}, + {'1': 'HEARTBEAT_STATUS_UNHEALTHY', '2': 2}, + ], +}; + +/// Descriptor for `HeartbeatStatus`. Decode as a `google.protobuf.EnumDescriptorProto`. +final $typed_data.Uint8List heartbeatStatusDescriptor = $convert.base64Decode( + 'Cg9IZWFydGJlYXRTdGF0dXMSIAocSEVBUlRCRUFUX1NUQVRVU19VTlNQRUNJRklFRBAAEhwKGE' + 'hFQVJUQkVBVF9TVEFUVVNfSEVBTFRIWRABEh4KGkhFQVJUQkVBVF9TVEFUVVNfVU5IRUFMVEhZ' + 'EAI='); + +@$core.Deprecated('Use runnerCapabilityDescriptor instead') +const RunnerCapability$json = { + '1': 'RunnerCapability', + '2': [ + {'1': 'name', '3': 1, '4': 1, '5': 9, '10': 'name'}, + {'1': 'version', '3': 2, '4': 1, '5': 9, '10': 'version'}, + ], +}; + +/// Descriptor for `RunnerCapability`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List runnerCapabilityDescriptor = $convert.base64Decode( + 'ChBSdW5uZXJDYXBhYmlsaXR5EhIKBG5hbWUYASABKAlSBG5hbWUSGAoHdmVyc2lvbhgCIAEoCV' + 'IHdmVyc2lvbg=='); + +@$core.Deprecated('Use commandCatalogSummaryDescriptor instead') +const CommandCatalogSummary$json = { + '1': 'CommandCatalogSummary', + '2': [ + {'1': 'command_types', '3': 1, '4': 3, '5': 9, '10': 'commandTypes'}, + ], +}; + +/// Descriptor for `CommandCatalogSummary`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List commandCatalogSummaryDescriptor = $convert.base64Decode( + 'ChVDb21tYW5kQ2F0YWxvZ1N1bW1hcnkSIwoNY29tbWFuZF90eXBlcxgBIAMoCVIMY29tbWFuZF' + 'R5cGVz'); + +@$core.Deprecated('Use registerRunnerRequestDescriptor instead') +const RegisterRunnerRequest$json = { + '1': 'RegisterRunnerRequest', + '2': [ + {'1': 'enrollment_token', '3': 1, '4': 1, '5': 9, '10': 'enrollmentToken'}, + {'1': 'runner_id', '3': 2, '4': 1, '5': 9, '10': 'runnerId'}, + {'1': 'alias', '3': 3, '4': 1, '5': 9, '10': 'alias'}, + {'1': 'protocol_version', '3': 4, '4': 1, '5': 9, '10': 'protocolVersion'}, + { + '1': 'capability', + '3': 5, + '4': 1, + '5': 11, + '6': '.oto.runner.v1.RunnerCapability', + '10': 'capability' + }, + { + '1': 'command_catalog', + '3': 6, + '4': 1, + '5': 11, + '6': '.oto.runner.v1.CommandCatalogSummary', + '10': 'commandCatalog' + }, + ], +}; + +/// Descriptor for `RegisterRunnerRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List registerRunnerRequestDescriptor = $convert.base64Decode( + 'ChVSZWdpc3RlclJ1bm5lclJlcXVlc3QSKQoQZW5yb2xsbWVudF90b2tlbhgBIAEoCVIPZW5yb2' + 'xsbWVudFRva2VuEhsKCXJ1bm5lcl9pZBgCIAEoCVIIcnVubmVySWQSFAoFYWxpYXMYAyABKAlS' + 'BWFsaWFzEikKEHByb3RvY29sX3ZlcnNpb24YBCABKAlSD3Byb3RvY29sVmVyc2lvbhI/CgpjYX' + 'BhYmlsaXR5GAUgASgLMh8ub3RvLnJ1bm5lci52MS5SdW5uZXJDYXBhYmlsaXR5UgpjYXBhYmls' + 'aXR5Ek0KD2NvbW1hbmRfY2F0YWxvZxgGIAEoCzIkLm90by5ydW5uZXIudjEuQ29tbWFuZENhdG' + 'Fsb2dTdW1tYXJ5Ug5jb21tYW5kQ2F0YWxvZw=='); + +@$core.Deprecated('Use registerRunnerResponseDescriptor instead') +const RegisterRunnerResponse$json = { + '1': 'RegisterRunnerResponse', + '2': [ + {'1': 'accepted', '3': 1, '4': 1, '5': 8, '10': 'accepted'}, + {'1': 'reject_reason', '3': 2, '4': 1, '5': 9, '10': 'rejectReason'}, + {'1': 'runner_id', '3': 3, '4': 1, '5': 9, '10': 'runnerId'}, + {'1': 'alias', '3': 4, '4': 1, '5': 9, '10': 'alias'}, + ], +}; + +/// Descriptor for `RegisterRunnerResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List registerRunnerResponseDescriptor = $convert.base64Decode( + 'ChZSZWdpc3RlclJ1bm5lclJlc3BvbnNlEhoKCGFjY2VwdGVkGAEgASgIUghhY2NlcHRlZBIjCg' + '1yZWplY3RfcmVhc29uGAIgASgJUgxyZWplY3RSZWFzb24SGwoJcnVubmVyX2lkGAMgASgJUghy' + 'dW5uZXJJZBIUCgVhbGlhcxgEIAEoCVIFYWxpYXM='); + +@$core.Deprecated('Use heartbeatRequestDescriptor instead') +const HeartbeatRequest$json = { + '1': 'HeartbeatRequest', + '2': [ + {'1': 'runner_id', '3': 1, '4': 1, '5': 9, '10': 'runnerId'}, + { + '1': 'status', + '3': 2, + '4': 1, + '5': 14, + '6': '.oto.runner.v1.HeartbeatStatus', + '10': 'status' + }, + ], +}; + +/// Descriptor for `HeartbeatRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List heartbeatRequestDescriptor = $convert.base64Decode( + 'ChBIZWFydGJlYXRSZXF1ZXN0EhsKCXJ1bm5lcl9pZBgBIAEoCVIIcnVubmVySWQSNgoGc3RhdH' + 'VzGAIgASgOMh4ub3RvLnJ1bm5lci52MS5IZWFydGJlYXRTdGF0dXNSBnN0YXR1cw=='); + +@$core.Deprecated('Use heartbeatResponseDescriptor instead') +const HeartbeatResponse$json = { + '1': 'HeartbeatResponse', + '2': [ + {'1': 'success', '3': 1, '4': 1, '5': 8, '10': 'success'}, + {'1': 'error_message', '3': 2, '4': 1, '5': 9, '10': 'errorMessage'}, + ], +}; + +/// Descriptor for `HeartbeatResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List heartbeatResponseDescriptor = $convert.base64Decode( + 'ChFIZWFydGJlYXRSZXNwb25zZRIYCgdzdWNjZXNzGAEgASgIUgdzdWNjZXNzEiMKDWVycm9yX2' + '1lc3NhZ2UYAiABKAlSDGVycm9yTWVzc2FnZQ=='); + +@$core.Deprecated('Use bootstrapCommandRequestDescriptor instead') +const BootstrapCommandRequest$json = { + '1': 'BootstrapCommandRequest', + '2': [ + {'1': 'runner_id', '3': 1, '4': 1, '5': 9, '10': 'runnerId'}, + ], +}; + +/// Descriptor for `BootstrapCommandRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List bootstrapCommandRequestDescriptor = + $convert.base64Decode( + 'ChdCb290c3RyYXBDb21tYW5kUmVxdWVzdBIbCglydW5uZXJfaWQYASABKAlSCHJ1bm5lcklk'); + +@$core.Deprecated('Use bootstrapCommandResponseDescriptor instead') +const BootstrapCommandResponse$json = { + '1': 'BootstrapCommandResponse', + '2': [ + { + '1': 'bootstrap_command', + '3': 1, + '4': 1, + '5': 9, + '10': 'bootstrapCommand' + }, + ], +}; + +/// Descriptor for `BootstrapCommandResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List bootstrapCommandResponseDescriptor = + $convert.base64Decode( + 'ChhCb290c3RyYXBDb21tYW5kUmVzcG9uc2USKwoRYm9vdHN0cmFwX2NvbW1hbmQYASABKAlSEG' + 'Jvb3RzdHJhcENvbW1hbmQ='); diff --git a/apps/runner/test/oto_agent_migration_plan_test.dart b/apps/runner/test/oto_agent_migration_plan_test.dart new file mode 100644 index 0000000..9e9b4b5 --- /dev/null +++ b/apps/runner/test/oto_agent_migration_plan_test.dart @@ -0,0 +1,164 @@ +import 'package:test/test.dart'; + +class SmokeFixtureBoundary { + final String name; + final String target; + final bool startsExternalRepo; + final List requiredInputs; + final List expectedEvidence; + final List forbiddenDependencies; + + const SmokeFixtureBoundary({ + required this.name, + required this.target, + required this.startsExternalRepo, + required this.requiredInputs, + required this.expectedEvidence, + required this.forbiddenDependencies, + }); +} + +class SmokeMigrationStep { + final String id; + final String description; + + const SmokeMigrationStep(this.id, this.description); +} + +class OtoServerSmokeMigrationPlan { + final SmokeFixtureBoundary legacySource; + final SmokeFixtureBoundary otoServerTarget; + final List steps; + + const OtoServerSmokeMigrationPlan({ + required this.legacySource, + required this.otoServerTarget, + required this.steps, + }); +} + +const migrationPlan = OtoServerSmokeMigrationPlan( + legacySource: SmokeFixtureBoundary( + name: 'legacy-iop-edge-online-smoke', + target: 'apps/runner/test/oto_iop_connection_smoke_test.dart', + startsExternalRepo: true, + requiredInputs: [ + 'IOP_REPO_ROOT or workspace sibling iop checkout', + 'iop Edge config fixture', + 'iop runtime RegisterRequest/RegisterResponse protobuf', + ], + expectedEvidence: [ + 'registration accepted by iop Edge', + 'first heartbeat transitions the iop Edge node record to online', + ], + forbiddenDependencies: [], + ), + otoServerTarget: SmokeFixtureBoundary( + name: 'oto-server-runner-online-smoke', + target: 'future apps/runner test against services/core', + startsExternalRepo: false, + requiredInputs: [ + 'loopback OTO Core listener with dynamic port', + 'temporary runner install/workspace/log directories', + 'runner enrollment token fixture owned by services/core', + 'OTO Server registration and heartbeat contract fixture', + ], + expectedEvidence: [ + 'runner registration accepted by OTO Server', + 'runner id and alias recorded in OTO Server registry', + 'first heartbeat or equivalent online signal marks runner online', + ], + forbiddenDependencies: [ + 'IOP_REPO_ROOT', + '../iop checkout', + 'iop Edge process', + 'iop runtime protobuf as the acceptance contract', + ], + ), + steps: [ + SmokeMigrationStep( + 'freeze-legacy', + 'Keep the iop smoke as migration source evidence until the OTO Server ' + 'target fixture reaches parity.', + ), + SmokeMigrationStep( + 'define-core-fixture', + 'Add a services/core test fixture that can issue enrollment tokens, ' + 'accept runner registration, and expose runner online state.', + ), + SmokeMigrationStep( + 'define-runner-fixture', + 'Start OTO Core on loopback from the runner test, create temporary ' + 'runtime directories, and point AgentConfig at the OTO Server target.', + ), + SmokeMigrationStep( + 'replace-contract', + 'Replace iop RegisterRequest/RegisterResponse acceptance with an OTO ' + 'Server owned registration and heartbeat contract.', + ), + SmokeMigrationStep( + 'retire-iop-smoke', + 'Remove the iop smoke from required acceptance once the OTO Server smoke ' + 'proves registration accepted and online evidence.', + ), + ], +); + +void main() { + group('OTO Server migration smoke plan', () { + test('keeps the existing iop smoke as legacy source evidence only', () { + expect(migrationPlan.legacySource.name, 'legacy-iop-edge-online-smoke'); + expect(migrationPlan.legacySource.startsExternalRepo, isTrue); + expect( + migrationPlan.legacySource.requiredInputs, + contains('IOP_REPO_ROOT or workspace sibling iop checkout'), + ); + expect( + migrationPlan.legacySource.expectedEvidence, + contains('registration accepted by iop Edge'), + ); + }); + + test('defines an OTO Server target fixture without iop dependencies', () { + final target = migrationPlan.otoServerTarget; + + expect(target.name, 'oto-server-runner-online-smoke'); + expect(target.startsExternalRepo, isFalse); + expect( + target.requiredInputs, + contains('loopback OTO Core listener with dynamic port'), + ); + expect( + target.requiredInputs, + contains('runner enrollment token fixture owned by services/core'), + ); + expect( + target.expectedEvidence, + contains('runner registration accepted by OTO Server'), + ); + expect(target.forbiddenDependencies, contains('IOP_REPO_ROOT')); + expect(target.forbiddenDependencies, contains('../iop checkout')); + expect(target.forbiddenDependencies, contains('iop Edge process')); + expect( + target.forbiddenDependencies, + contains('iop runtime protobuf as the acceptance contract'), + ); + }); + + test('orders the handoff before retiring the iop smoke', () { + final stepIds = migrationPlan.steps.map((step) => step.id).toList(); + + expect(stepIds, [ + 'freeze-legacy', + 'define-core-fixture', + 'define-runner-fixture', + 'replace-contract', + 'retire-iop-smoke', + ]); + expect( + migrationPlan.steps.last.description, + contains('Remove the iop smoke from required acceptance'), + ); + }); + }); +} diff --git a/apps/runner/test/oto_iop_connection_smoke_test.dart b/apps/runner/test/oto_iop_connection_smoke_test.dart index ae12f91..6d43e40 100644 --- a/apps/runner/test/oto_iop_connection_smoke_test.dart +++ b/apps/runner/test/oto_iop_connection_smoke_test.dart @@ -10,6 +10,12 @@ const _token = 'oto-smoke-token'; const _nodeId = 'oto-smoke-node'; const _nodeAlias = 'oto-smoke'; +// Legacy iop Edge smoke retained as migration source evidence. +// +// The OTO Server replacement fixture boundary is captured in +// `oto_agent_migration_plan_test.dart`; this test should stop being required +// once that target smoke proves runner registration and online state. + /// iop repo root used to run the Edge under test. /// /// Defaults to the workspace sibling `iop` checkout. From this package at @@ -40,27 +46,21 @@ void main() { // the smoke tails it for the node.online lifecycle marker. final edgeLog = File('${workDir.path}/edge.log'); final config = File('${workDir.path}/edge.yaml'); - await config.writeAsString(_edgeConfig( - edgePort: edgePort, - metricsPort: metricsPort, - bootstrapPort: bootstrapPort, - logPath: edgeLog.path, - root: workDir.path, - )); + await config.writeAsString( + _edgeConfig( + edgePort: edgePort, + metricsPort: metricsPort, + bootstrapPort: bootstrapPort, + logPath: edgeLog.path, + root: workDir.path, + ), + ); final edge = await Process.start( 'go', - [ - 'run', - './apps/edge/cmd/edge', - 'serve', - '--config', - config.path, - ], + ['run', './apps/edge/cmd/edge', 'serve', '--config', config.path], workingDirectory: iopRepoRoot, - environment: { - 'GOCACHE': goCache, - }, + environment: {'GOCACHE': goCache}, ); final output = StringBuffer(); @@ -84,9 +84,7 @@ void main() { alias: _nodeAlias, enrollmentToken: _token, ), - edge: EdgeConnectionConfig( - url: '$_host:$edgePort', - ), + edge: EdgeConnectionConfig(url: '$_host:$edgePort'), runtime: AgentRuntimeConfig( installDir: '${workDir.path}/install', workspaceRoot: '${workDir.path}/workspace', @@ -110,8 +108,10 @@ void main() { ); // ignore: avoid_print - print('[oto-smoke] registration accepted ' - 'node=${result.nodeId} alias=${result.alias}'); + print( + '[oto-smoke] registration accepted ' + 'node=${result.nodeId} alias=${result.alias}', + ); // After accepted, wait for the Edge to record the first heartbeat and // transition the oto-agent node to online (node.online event). The @@ -212,18 +212,18 @@ Future _waitForLogMarker( } await Future.delayed(const Duration(milliseconds: 100)); } - final logTail = - logFile.existsSync() ? logFile.readAsStringSync() : '(no log)'; - fail('timed out waiting for "$marker" in Edge log:\n' - 'edge log:\n$logTail\nedge stderr:\n$output'); + final logTail = logFile.existsSync() + ? logFile.readAsStringSync() + : '(no log)'; + fail( + 'timed out waiting for "$marker" in Edge log:\n' + 'edge log:\n$logTail\nedge stderr:\n$output', + ); } Future _tryExitCode(Process process) { return process.exitCode - .timeout( - Duration.zero, - onTimeout: () => -1, - ) + .timeout(Duration.zero, onTimeout: () => -1) .then((code) => code == -1 ? null : code); } diff --git a/proto/oto/runner.proto b/proto/oto/runner.proto new file mode 100644 index 0000000..b095616 --- /dev/null +++ b/proto/oto/runner.proto @@ -0,0 +1,54 @@ +syntax = "proto3"; + +package oto.runner.v1; + +option go_package = "github.com/toki/oto/services/core/oto;otopb"; + +message RunnerCapability { + string name = 1; + string version = 2; +} + +message CommandCatalogSummary { + repeated string command_types = 1; +} + +message RegisterRunnerRequest { + string enrollment_token = 1; + string runner_id = 2; + string alias = 3; + string protocol_version = 4; + RunnerCapability capability = 5; + CommandCatalogSummary command_catalog = 6; +} + +message RegisterRunnerResponse { + bool accepted = 1; + string reject_reason = 2; + string runner_id = 3; + string alias = 4; +} + +enum HeartbeatStatus { + HEARTBEAT_STATUS_UNSPECIFIED = 0; + HEARTBEAT_STATUS_HEALTHY = 1; + HEARTBEAT_STATUS_UNHEALTHY = 2; +} + +message HeartbeatRequest { + string runner_id = 1; + HeartbeatStatus status = 2; +} + +message HeartbeatResponse { + bool success = 1; + string error_message = 2; +} + +message BootstrapCommandRequest { + string runner_id = 1; +} + +message BootstrapCommandResponse { + string bootstrap_command = 1; +} diff --git a/services/core/go.mod b/services/core/go.mod index 9329bc0..af5f597 100644 --- a/services/core/go.mod +++ b/services/core/go.mod @@ -1,3 +1,5 @@ module github.com/toki/oto/services/core go 1.26 + +require google.golang.org/protobuf v1.36.11 diff --git a/services/core/go.sum b/services/core/go.sum new file mode 100644 index 0000000..296be18 --- /dev/null +++ b/services/core/go.sum @@ -0,0 +1,4 @@ +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= diff --git a/services/core/oto/runner.pb.go b/services/core/oto/runner.pb.go new file mode 100644 index 0000000..89a2ca8 --- /dev/null +++ b/services/core/oto/runner.pb.go @@ -0,0 +1,611 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v5.29.3 +// source: oto/runner.proto + +package otopb + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type HeartbeatStatus int32 + +const ( + HeartbeatStatus_HEARTBEAT_STATUS_UNSPECIFIED HeartbeatStatus = 0 + HeartbeatStatus_HEARTBEAT_STATUS_HEALTHY HeartbeatStatus = 1 + HeartbeatStatus_HEARTBEAT_STATUS_UNHEALTHY HeartbeatStatus = 2 +) + +// Enum value maps for HeartbeatStatus. +var ( + HeartbeatStatus_name = map[int32]string{ + 0: "HEARTBEAT_STATUS_UNSPECIFIED", + 1: "HEARTBEAT_STATUS_HEALTHY", + 2: "HEARTBEAT_STATUS_UNHEALTHY", + } + HeartbeatStatus_value = map[string]int32{ + "HEARTBEAT_STATUS_UNSPECIFIED": 0, + "HEARTBEAT_STATUS_HEALTHY": 1, + "HEARTBEAT_STATUS_UNHEALTHY": 2, + } +) + +func (x HeartbeatStatus) Enum() *HeartbeatStatus { + p := new(HeartbeatStatus) + *p = x + return p +} + +func (x HeartbeatStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (HeartbeatStatus) Descriptor() protoreflect.EnumDescriptor { + return file_oto_runner_proto_enumTypes[0].Descriptor() +} + +func (HeartbeatStatus) Type() protoreflect.EnumType { + return &file_oto_runner_proto_enumTypes[0] +} + +func (x HeartbeatStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use HeartbeatStatus.Descriptor instead. +func (HeartbeatStatus) EnumDescriptor() ([]byte, []int) { + return file_oto_runner_proto_rawDescGZIP(), []int{0} +} + +type RunnerCapability struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RunnerCapability) Reset() { + *x = RunnerCapability{} + mi := &file_oto_runner_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RunnerCapability) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RunnerCapability) ProtoMessage() {} + +func (x *RunnerCapability) ProtoReflect() protoreflect.Message { + mi := &file_oto_runner_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RunnerCapability.ProtoReflect.Descriptor instead. +func (*RunnerCapability) Descriptor() ([]byte, []int) { + return file_oto_runner_proto_rawDescGZIP(), []int{0} +} + +func (x *RunnerCapability) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *RunnerCapability) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +type CommandCatalogSummary struct { + state protoimpl.MessageState `protogen:"open.v1"` + CommandTypes []string `protobuf:"bytes,1,rep,name=command_types,json=commandTypes,proto3" json:"command_types,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CommandCatalogSummary) Reset() { + *x = CommandCatalogSummary{} + mi := &file_oto_runner_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CommandCatalogSummary) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CommandCatalogSummary) ProtoMessage() {} + +func (x *CommandCatalogSummary) ProtoReflect() protoreflect.Message { + mi := &file_oto_runner_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CommandCatalogSummary.ProtoReflect.Descriptor instead. +func (*CommandCatalogSummary) Descriptor() ([]byte, []int) { + return file_oto_runner_proto_rawDescGZIP(), []int{1} +} + +func (x *CommandCatalogSummary) GetCommandTypes() []string { + if x != nil { + return x.CommandTypes + } + return nil +} + +type RegisterRunnerRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + EnrollmentToken string `protobuf:"bytes,1,opt,name=enrollment_token,json=enrollmentToken,proto3" json:"enrollment_token,omitempty"` + RunnerId string `protobuf:"bytes,2,opt,name=runner_id,json=runnerId,proto3" json:"runner_id,omitempty"` + Alias string `protobuf:"bytes,3,opt,name=alias,proto3" json:"alias,omitempty"` + ProtocolVersion string `protobuf:"bytes,4,opt,name=protocol_version,json=protocolVersion,proto3" json:"protocol_version,omitempty"` + Capability *RunnerCapability `protobuf:"bytes,5,opt,name=capability,proto3" json:"capability,omitempty"` + CommandCatalog *CommandCatalogSummary `protobuf:"bytes,6,opt,name=command_catalog,json=commandCatalog,proto3" json:"command_catalog,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RegisterRunnerRequest) Reset() { + *x = RegisterRunnerRequest{} + mi := &file_oto_runner_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RegisterRunnerRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RegisterRunnerRequest) ProtoMessage() {} + +func (x *RegisterRunnerRequest) ProtoReflect() protoreflect.Message { + mi := &file_oto_runner_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RegisterRunnerRequest.ProtoReflect.Descriptor instead. +func (*RegisterRunnerRequest) Descriptor() ([]byte, []int) { + return file_oto_runner_proto_rawDescGZIP(), []int{2} +} + +func (x *RegisterRunnerRequest) GetEnrollmentToken() string { + if x != nil { + return x.EnrollmentToken + } + return "" +} + +func (x *RegisterRunnerRequest) GetRunnerId() string { + if x != nil { + return x.RunnerId + } + return "" +} + +func (x *RegisterRunnerRequest) GetAlias() string { + if x != nil { + return x.Alias + } + return "" +} + +func (x *RegisterRunnerRequest) GetProtocolVersion() string { + if x != nil { + return x.ProtocolVersion + } + return "" +} + +func (x *RegisterRunnerRequest) GetCapability() *RunnerCapability { + if x != nil { + return x.Capability + } + return nil +} + +func (x *RegisterRunnerRequest) GetCommandCatalog() *CommandCatalogSummary { + if x != nil { + return x.CommandCatalog + } + return nil +} + +type RegisterRunnerResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Accepted bool `protobuf:"varint,1,opt,name=accepted,proto3" json:"accepted,omitempty"` + RejectReason string `protobuf:"bytes,2,opt,name=reject_reason,json=rejectReason,proto3" json:"reject_reason,omitempty"` + RunnerId string `protobuf:"bytes,3,opt,name=runner_id,json=runnerId,proto3" json:"runner_id,omitempty"` + Alias string `protobuf:"bytes,4,opt,name=alias,proto3" json:"alias,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RegisterRunnerResponse) Reset() { + *x = RegisterRunnerResponse{} + mi := &file_oto_runner_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RegisterRunnerResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RegisterRunnerResponse) ProtoMessage() {} + +func (x *RegisterRunnerResponse) ProtoReflect() protoreflect.Message { + mi := &file_oto_runner_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RegisterRunnerResponse.ProtoReflect.Descriptor instead. +func (*RegisterRunnerResponse) Descriptor() ([]byte, []int) { + return file_oto_runner_proto_rawDescGZIP(), []int{3} +} + +func (x *RegisterRunnerResponse) GetAccepted() bool { + if x != nil { + return x.Accepted + } + return false +} + +func (x *RegisterRunnerResponse) GetRejectReason() string { + if x != nil { + return x.RejectReason + } + return "" +} + +func (x *RegisterRunnerResponse) GetRunnerId() string { + if x != nil { + return x.RunnerId + } + return "" +} + +func (x *RegisterRunnerResponse) GetAlias() string { + if x != nil { + return x.Alias + } + return "" +} + +type HeartbeatRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + RunnerId string `protobuf:"bytes,1,opt,name=runner_id,json=runnerId,proto3" json:"runner_id,omitempty"` + Status HeartbeatStatus `protobuf:"varint,2,opt,name=status,proto3,enum=oto.runner.v1.HeartbeatStatus" json:"status,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HeartbeatRequest) Reset() { + *x = HeartbeatRequest{} + mi := &file_oto_runner_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HeartbeatRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HeartbeatRequest) ProtoMessage() {} + +func (x *HeartbeatRequest) ProtoReflect() protoreflect.Message { + mi := &file_oto_runner_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HeartbeatRequest.ProtoReflect.Descriptor instead. +func (*HeartbeatRequest) Descriptor() ([]byte, []int) { + return file_oto_runner_proto_rawDescGZIP(), []int{4} +} + +func (x *HeartbeatRequest) GetRunnerId() string { + if x != nil { + return x.RunnerId + } + return "" +} + +func (x *HeartbeatRequest) GetStatus() HeartbeatStatus { + if x != nil { + return x.Status + } + return HeartbeatStatus_HEARTBEAT_STATUS_UNSPECIFIED +} + +type HeartbeatResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` + ErrorMessage string `protobuf:"bytes,2,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HeartbeatResponse) Reset() { + *x = HeartbeatResponse{} + mi := &file_oto_runner_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HeartbeatResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HeartbeatResponse) ProtoMessage() {} + +func (x *HeartbeatResponse) ProtoReflect() protoreflect.Message { + mi := &file_oto_runner_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HeartbeatResponse.ProtoReflect.Descriptor instead. +func (*HeartbeatResponse) Descriptor() ([]byte, []int) { + return file_oto_runner_proto_rawDescGZIP(), []int{5} +} + +func (x *HeartbeatResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +func (x *HeartbeatResponse) GetErrorMessage() string { + if x != nil { + return x.ErrorMessage + } + return "" +} + +type BootstrapCommandRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + RunnerId string `protobuf:"bytes,1,opt,name=runner_id,json=runnerId,proto3" json:"runner_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *BootstrapCommandRequest) Reset() { + *x = BootstrapCommandRequest{} + mi := &file_oto_runner_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BootstrapCommandRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BootstrapCommandRequest) ProtoMessage() {} + +func (x *BootstrapCommandRequest) ProtoReflect() protoreflect.Message { + mi := &file_oto_runner_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BootstrapCommandRequest.ProtoReflect.Descriptor instead. +func (*BootstrapCommandRequest) Descriptor() ([]byte, []int) { + return file_oto_runner_proto_rawDescGZIP(), []int{6} +} + +func (x *BootstrapCommandRequest) GetRunnerId() string { + if x != nil { + return x.RunnerId + } + return "" +} + +type BootstrapCommandResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + BootstrapCommand string `protobuf:"bytes,1,opt,name=bootstrap_command,json=bootstrapCommand,proto3" json:"bootstrap_command,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *BootstrapCommandResponse) Reset() { + *x = BootstrapCommandResponse{} + mi := &file_oto_runner_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BootstrapCommandResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BootstrapCommandResponse) ProtoMessage() {} + +func (x *BootstrapCommandResponse) ProtoReflect() protoreflect.Message { + mi := &file_oto_runner_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BootstrapCommandResponse.ProtoReflect.Descriptor instead. +func (*BootstrapCommandResponse) Descriptor() ([]byte, []int) { + return file_oto_runner_proto_rawDescGZIP(), []int{7} +} + +func (x *BootstrapCommandResponse) GetBootstrapCommand() string { + if x != nil { + return x.BootstrapCommand + } + return "" +} + +var File_oto_runner_proto protoreflect.FileDescriptor + +const file_oto_runner_proto_rawDesc = "" + + "\n" + + "\x10oto/runner.proto\x12\roto.runner.v1\"@\n" + + "\x10RunnerCapability\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n" + + "\aversion\x18\x02 \x01(\tR\aversion\"<\n" + + "\x15CommandCatalogSummary\x12#\n" + + "\rcommand_types\x18\x01 \x03(\tR\fcommandTypes\"\xb0\x02\n" + + "\x15RegisterRunnerRequest\x12)\n" + + "\x10enrollment_token\x18\x01 \x01(\tR\x0fenrollmentToken\x12\x1b\n" + + "\trunner_id\x18\x02 \x01(\tR\brunnerId\x12\x14\n" + + "\x05alias\x18\x03 \x01(\tR\x05alias\x12)\n" + + "\x10protocol_version\x18\x04 \x01(\tR\x0fprotocolVersion\x12?\n" + + "\n" + + "capability\x18\x05 \x01(\v2\x1f.oto.runner.v1.RunnerCapabilityR\n" + + "capability\x12M\n" + + "\x0fcommand_catalog\x18\x06 \x01(\v2$.oto.runner.v1.CommandCatalogSummaryR\x0ecommandCatalog\"\x8c\x01\n" + + "\x16RegisterRunnerResponse\x12\x1a\n" + + "\baccepted\x18\x01 \x01(\bR\baccepted\x12#\n" + + "\rreject_reason\x18\x02 \x01(\tR\frejectReason\x12\x1b\n" + + "\trunner_id\x18\x03 \x01(\tR\brunnerId\x12\x14\n" + + "\x05alias\x18\x04 \x01(\tR\x05alias\"g\n" + + "\x10HeartbeatRequest\x12\x1b\n" + + "\trunner_id\x18\x01 \x01(\tR\brunnerId\x126\n" + + "\x06status\x18\x02 \x01(\x0e2\x1e.oto.runner.v1.HeartbeatStatusR\x06status\"R\n" + + "\x11HeartbeatResponse\x12\x18\n" + + "\asuccess\x18\x01 \x01(\bR\asuccess\x12#\n" + + "\rerror_message\x18\x02 \x01(\tR\ferrorMessage\"6\n" + + "\x17BootstrapCommandRequest\x12\x1b\n" + + "\trunner_id\x18\x01 \x01(\tR\brunnerId\"G\n" + + "\x18BootstrapCommandResponse\x12+\n" + + "\x11bootstrap_command\x18\x01 \x01(\tR\x10bootstrapCommand*q\n" + + "\x0fHeartbeatStatus\x12 \n" + + "\x1cHEARTBEAT_STATUS_UNSPECIFIED\x10\x00\x12\x1c\n" + + "\x18HEARTBEAT_STATUS_HEALTHY\x10\x01\x12\x1e\n" + + "\x1aHEARTBEAT_STATUS_UNHEALTHY\x10\x02B-Z+github.com/toki/oto/services/core/oto;otopbb\x06proto3" + +var ( + file_oto_runner_proto_rawDescOnce sync.Once + file_oto_runner_proto_rawDescData []byte +) + +func file_oto_runner_proto_rawDescGZIP() []byte { + file_oto_runner_proto_rawDescOnce.Do(func() { + file_oto_runner_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_oto_runner_proto_rawDesc), len(file_oto_runner_proto_rawDesc))) + }) + return file_oto_runner_proto_rawDescData +} + +var file_oto_runner_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_oto_runner_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_oto_runner_proto_goTypes = []any{ + (HeartbeatStatus)(0), // 0: oto.runner.v1.HeartbeatStatus + (*RunnerCapability)(nil), // 1: oto.runner.v1.RunnerCapability + (*CommandCatalogSummary)(nil), // 2: oto.runner.v1.CommandCatalogSummary + (*RegisterRunnerRequest)(nil), // 3: oto.runner.v1.RegisterRunnerRequest + (*RegisterRunnerResponse)(nil), // 4: oto.runner.v1.RegisterRunnerResponse + (*HeartbeatRequest)(nil), // 5: oto.runner.v1.HeartbeatRequest + (*HeartbeatResponse)(nil), // 6: oto.runner.v1.HeartbeatResponse + (*BootstrapCommandRequest)(nil), // 7: oto.runner.v1.BootstrapCommandRequest + (*BootstrapCommandResponse)(nil), // 8: oto.runner.v1.BootstrapCommandResponse +} +var file_oto_runner_proto_depIdxs = []int32{ + 1, // 0: oto.runner.v1.RegisterRunnerRequest.capability:type_name -> oto.runner.v1.RunnerCapability + 2, // 1: oto.runner.v1.RegisterRunnerRequest.command_catalog:type_name -> oto.runner.v1.CommandCatalogSummary + 0, // 2: oto.runner.v1.HeartbeatRequest.status:type_name -> oto.runner.v1.HeartbeatStatus + 3, // [3:3] is the sub-list for method output_type + 3, // [3:3] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name +} + +func init() { file_oto_runner_proto_init() } +func file_oto_runner_proto_init() { + if File_oto_runner_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_oto_runner_proto_rawDesc), len(file_oto_runner_proto_rawDesc)), + NumEnums: 1, + NumMessages: 8, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_oto_runner_proto_goTypes, + DependencyIndexes: file_oto_runner_proto_depIdxs, + EnumInfos: file_oto_runner_proto_enumTypes, + MessageInfos: file_oto_runner_proto_msgTypes, + }.Build() + File_oto_runner_proto = out.File + file_oto_runner_proto_goTypes = nil + file_oto_runner_proto_depIdxs = nil +}