feat(control-plane): Server-Runner 계약을 추가한다
독립 Control Plane 전환의 선행 조건으로 Go server와 Dart runner가 공유할 wire 계약 원본과 생성물 경로가 필요하다. proto 생성 target, 생성된 Go/Dart protobuf, migration smoke 경계 문서화와 리뷰 완료 artifact를 함께 반영한다.
This commit is contained in:
parent
619c9e223f
commit
78c2260dc8
23 changed files with 3151 additions and 47 deletions
18
Makefile
18
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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 제품 구조로 전환한다.
|
||||
- [계획] 메시지 기반 빌드 에이전트
|
||||
|
|
|
|||
|
|
@ -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로 전환한다.
|
||||
|
||||
|
|
|
|||
|
|
@ -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는 미완료로 유지한다.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,155 @@
|
|||
<!-- task=m-control-plane-separation-migration/01_wire_contract plan=0 tag=API -->
|
||||
|
||||
# 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-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, 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로 이동한다.
|
||||
|
|
@ -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
|
||||
|
||||
- 없음
|
||||
|
||||
## 후속 작업
|
||||
|
||||
- 없음
|
||||
|
|
@ -0,0 +1,215 @@
|
|||
<!-- task=m-control-plane-separation-migration/01_wire_contract plan=0 tag=API -->
|
||||
|
||||
# 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`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
<!-- task=m-control-plane-separation-migration/02+01_runner_registration plan=0 tag=API -->
|
||||
|
||||
# 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-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고하고 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)
|
||||
```
|
||||
|
|
@ -0,0 +1,239 @@
|
|||
<!-- task=m-control-plane-separation-migration/02+01_runner_registration plan=0 tag=API -->
|
||||
|
||||
# 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, iop.RegisterResponse>(
|
||||
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`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
<!-- task=m-control-plane-separation-migration/03+02_online_state plan=0 tag=API -->
|
||||
|
||||
# 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)
|
||||
```
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
<!-- task=m-control-plane-separation-migration/03+02_online_state plan=0 tag=API -->
|
||||
|
||||
# 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`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
<!-- task=m-control-plane-separation-migration/04+02_bootstrap_command plan=0 tag=API -->
|
||||
|
||||
# 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)
|
||||
```
|
||||
|
|
@ -0,0 +1,228 @@
|
|||
<!-- task=m-control-plane-separation-migration/04+02_bootstrap_command plan=0 tag=API -->
|
||||
|
||||
# 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 <server>/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 <url> --agent-id <id> --enrollment-token <token> --release-base-url <url> [options]"
|
||||
28 --edge-url)
|
||||
132 edge:
|
||||
133 url: "$edge_url"
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```bash
|
||||
echo "Usage: $0 --server-url <url> --agent-id <id> --enrollment-token <token> --release-base-url <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`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
596
apps/runner/lib/oto/agent/oto/runner.pb.dart
Normal file
596
apps/runner/lib/oto/agent/oto/runner.pb.dart
Normal file
|
|
@ -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<RunnerCapability>(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<CommandCatalogSummary>(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<RunnerCapability>(5, _omitFieldNames ? '' : 'capability',
|
||||
subBuilder: RunnerCapability.create)
|
||||
..aOM<CommandCatalogSummary>(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<RegisterRunnerRequest>(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<RegisterRunnerResponse>(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<HeartbeatStatus>(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<HeartbeatRequest>(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<HeartbeatResponse>(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<BootstrapCommandRequest>(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<BootstrapCommandResponse>(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');
|
||||
40
apps/runner/lib/oto/agent/oto/runner.pbenum.dart
Normal file
40
apps/runner/lib/oto/agent/oto/runner.pbenum.dart
Normal file
|
|
@ -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<HeartbeatStatus> values = <HeartbeatStatus>[
|
||||
HEARTBEAT_STATUS_UNSPECIFIED,
|
||||
HEARTBEAT_STATUS_HEALTHY,
|
||||
HEARTBEAT_STATUS_UNHEALTHY,
|
||||
];
|
||||
|
||||
static final $core.List<HeartbeatStatus?> _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');
|
||||
180
apps/runner/lib/oto/agent/oto/runner.pbjson.dart
Normal file
180
apps/runner/lib/oto/agent/oto/runner.pbjson.dart
Normal file
|
|
@ -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=');
|
||||
164
apps/runner/test/oto_agent_migration_plan_test.dart
Normal file
164
apps/runner/test/oto_agent_migration_plan_test.dart
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
import 'package:test/test.dart';
|
||||
|
||||
class SmokeFixtureBoundary {
|
||||
final String name;
|
||||
final String target;
|
||||
final bool startsExternalRepo;
|
||||
final List<String> requiredInputs;
|
||||
final List<String> expectedEvidence;
|
||||
final List<String> 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<SmokeMigrationStep> 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'),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -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<String> _waitForLogMarker(
|
|||
}
|
||||
await Future<void>.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<int?> _tryExitCode(Process process) {
|
||||
return process.exitCode
|
||||
.timeout(
|
||||
Duration.zero,
|
||||
onTimeout: () => -1,
|
||||
)
|
||||
.timeout(Duration.zero, onTimeout: () => -1)
|
||||
.then((code) => code == -1 ? null : code);
|
||||
}
|
||||
|
||||
|
|
|
|||
54
proto/oto/runner.proto
Normal file
54
proto/oto/runner.proto
Normal file
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
module github.com/toki/oto/services/core
|
||||
|
||||
go 1.26
|
||||
|
||||
require google.golang.org/protobuf v1.36.11
|
||||
|
|
|
|||
4
services/core/go.sum
Normal file
4
services/core/go.sum
Normal file
|
|
@ -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=
|
||||
611
services/core/oto/runner.pb.go
Normal file
611
services/core/oto/runner.pb.go
Normal file
|
|
@ -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
|
||||
}
|
||||
Loading…
Reference in a new issue