From 0f1de7d81df9b0edc2fd496ed70380fb35474fab Mon Sep 17 00:00:00 2001 From: toki Date: Thu, 18 Jun 2026 09:19:53 +0900 Subject: [PATCH] feat: control-plane-operator-actions milestone and SDD additions - Update PHASE.md with operator actions milestone - Add control-plane-operator-actions milestone details - Add SDD documentation for control-plane-product-surface - Add agent-task for control-plane-operator-actions (G06, G07) --- .../control-plane-product-surface/PHASE.md | 2 +- .../control-plane-operator-actions.md | 13 +- .../control-plane-operator-actions/SDD.md | 117 ++++++ .../01_job_create/CODE_REVIEW-local-G06.md | 154 ++++++++ .../01_job_create/PLAN-local-G06.md | 333 ++++++++++++++++++ .../CODE_REVIEW-local-G07.md | 155 ++++++++ .../02+01_execution_actions/PLAN-local-G07.md | 297 ++++++++++++++++ .../CODE_REVIEW-local-G06.md | 154 ++++++++ .../03+01_runner_actions/PLAN-local-G06.md | 287 +++++++++++++++ 9 files changed, 1505 insertions(+), 7 deletions(-) create mode 100644 agent-roadmap/sdd/control-plane-product-surface/control-plane-operator-actions/SDD.md create mode 100644 agent-task/m-control-plane-operator-actions/01_job_create/CODE_REVIEW-local-G06.md create mode 100644 agent-task/m-control-plane-operator-actions/01_job_create/PLAN-local-G06.md create mode 100644 agent-task/m-control-plane-operator-actions/02+01_execution_actions/CODE_REVIEW-local-G07.md create mode 100644 agent-task/m-control-plane-operator-actions/02+01_execution_actions/PLAN-local-G07.md create mode 100644 agent-task/m-control-plane-operator-actions/03+01_runner_actions/CODE_REVIEW-local-G06.md create mode 100644 agent-task/m-control-plane-operator-actions/03+01_runner_actions/PLAN-local-G06.md diff --git a/agent-roadmap/phase/control-plane-product-surface/PHASE.md b/agent-roadmap/phase/control-plane-product-surface/PHASE.md index 47dfb9b..ab98ea6 100644 --- a/agent-roadmap/phase/control-plane-product-surface/PHASE.md +++ b/agent-roadmap/phase/control-plane-product-surface/PHASE.md @@ -24,7 +24,7 @@ - [완료] Control Plane API 데이터 바인딩 - 경로: `agent-roadmap/archive/phase/control-plane-product-surface/milestones/control-plane-api-data-binding.md` - 요약: UI 스켈레톤에 기존 OTO Core runner/job/execution/log/artifact API의 read path를 연결하고, 실제 데이터가 비어 있거나 실패할 때의 상태 표현을 검증한다. -- [계획] Control Plane 운영 액션 표면 +- [진행중] Control Plane 운영 액션 표면 - 경로: `agent-roadmap/phase/control-plane-product-surface/milestones/control-plane-operator-actions.md` - 요약: 기존 OTO Core write endpoint를 UI/adapter action 표면으로 연결하되, 상태 전이와 실패/확인 UX는 SDD로 먼저 잠근다. diff --git a/agent-roadmap/phase/control-plane-product-surface/milestones/control-plane-operator-actions.md b/agent-roadmap/phase/control-plane-product-surface/milestones/control-plane-operator-actions.md index ff49c32..b679844 100644 --- a/agent-roadmap/phase/control-plane-product-surface/milestones/control-plane-operator-actions.md +++ b/agent-roadmap/phase/control-plane-product-surface/milestones/control-plane-operator-actions.md @@ -12,7 +12,7 @@ Flutter 콘솔의 read-only 화면 위에 기존 OTO Core write endpoint를 사 ## 상태 -[계획] +[진행중] ## 승격 조건 @@ -20,13 +20,13 @@ Flutter 콘솔의 read-only 화면 위에 기존 OTO Core write endpoint를 사 ## 구현 잠금 -- 상태: 잠금 +- 상태: 해제 - SDD: 필요 - SDD 문서: `agent-roadmap/sdd/control-plane-product-surface/control-plane-operator-actions/SDD.md` - SDD 사유: UI에서 Core write endpoint를 호출하며 job/execution/runner lifecycle 상태 전이, 중복 요청, 실패/재시도, 사용자 확인 UX 경계를 먼저 고정해야 한다. - 잠금 해제 조건: - - [ ] SDD 상태가 `[승인됨]`이고 SDD 잠금이 해제되어 있다. - - [ ] SDD `USER_REVIEW.md`가 없거나 승인/해결되었다. + - [x] SDD 상태가 `[승인됨]`이고 SDD 잠금이 해제되어 있다. + - [x] SDD `USER_REVIEW.md`가 없거나 승인/해결되었다. - 결정 필요: 없음 ## 범위 @@ -42,7 +42,7 @@ Flutter 콘솔의 read-only 화면 위에 기존 OTO Core write endpoint를 사 기존 Control Plane read surface 위에 운영자가 실행할 수 있는 쓰기 액션의 안전한 UI/adapter 경계를 만든다. -- [ ] [action-sdd] job/execution/runner write action의 source of truth, 상태 전이, 실패/재시도, 사용자 확인 UX, 검증 evidence를 SDD로 고정한다. 검증: `agent-roadmap/sdd/control-plane-product-surface/control-plane-operator-actions/SDD.md`가 승인되고 사용자 리뷰가 남아 있지 않다. +- [x] [action-sdd] job/execution/runner write action의 source of truth, 상태 전이, 실패/재시도, 사용자 확인 UX, 검증 evidence를 SDD로 고정한다. 검증: `agent-roadmap/sdd/control-plane-product-surface/control-plane-operator-actions/SDD.md`가 승인되고 사용자 리뷰가 남아 있지 않다. - [ ] [job-create] 기존 `POST /api/v1/jobs` 계약을 사용하는 job 생성 action adapter와 UI 상태를 연결한다. 검증: `cd apps/client && flutter test`, `cd packages/flutter/oto_console && flutter test`에서 job create action 상태 테스트가 통과한다. - [ ] [execution-actions] 기존 execution/runner write endpoint를 사용하는 취소, report/log/artifact action 진입 상태를 연결한다. 검증: `cd apps/client && flutter test`, `cd packages/flutter/oto_console && flutter test`에서 execution action 상태 테스트가 통과한다. - [ ] [runner-actions] 기존 runner self-update/status action 표면을 연결하고 실패/대기/완료 상태를 표시한다. 검증: `cd apps/client && flutter test`, `cd packages/flutter/oto_console && flutter test`에서 runner action 상태 테스트가 통과한다. @@ -66,6 +66,7 @@ Flutter 콘솔의 read-only 화면 위에 기존 OTO Core write endpoint를 사 - 관련 경로: `apps/client/**`, `packages/flutter/oto_console/**`, `services/core/internal/httpserver/**` - 표준선(선택): `OtoConsoleConfig.serverHttpUrl`을 API base URL로 유지하고, UI 패키지는 transport 구현보다 상태/표현 contract에 집중한다. - 표준선(선택): Core write endpoint는 기존 route와 DTO를 우선 소비하고, schema 확장이 필요하면 SDD에서 후속 계약 변경으로 분리한다. +- 진행 메모: [action-sdd] `agent-roadmap/sdd/control-plane-product-surface/control-plane-operator-actions/SDD.md`를 `[승인됨]`, SDD 잠금 해제, 사용자 리뷰 없음으로 작성했다. (2026-06-18) - 선행 작업: `Control Plane API 데이터 바인딩` - 후속 작업: 인증/권한, 감사 로그, 운영 대시보드 고도화 -- 확인 필요: SDD gate +- 확인 필요: 없음 diff --git a/agent-roadmap/sdd/control-plane-product-surface/control-plane-operator-actions/SDD.md b/agent-roadmap/sdd/control-plane-product-surface/control-plane-operator-actions/SDD.md new file mode 100644 index 0000000..1c89f4b --- /dev/null +++ b/agent-roadmap/sdd/control-plane-product-surface/control-plane-operator-actions/SDD.md @@ -0,0 +1,117 @@ +# SDD: Control Plane 운영 액션 표면 + +## 위치 + +- Milestone: `agent-roadmap/phase/control-plane-product-surface/milestones/control-plane-operator-actions.md` +- Phase: `agent-roadmap/phase/control-plane-product-surface/PHASE.md` + +## 상태 + +[승인됨] + +## SDD 잠금 + +- 상태: 해제 +- 사용자 리뷰: 없음 +- 잠금 항목: + - 없음 + +## 문제 / 비목표 + +- 문제: read-only Control Plane 화면 위에 Core write endpoint를 붙이면 job, execution, runner 상태를 사용자가 직접 바꾸게 된다. 따라서 action source of truth, 확인 UX, 중복 제출 방지, 성공/실패/지연 상태 표시, evidence 연결을 구현 전에 고정해야 한다. +- 비목표: + - 신규 Core API, proto, schema, persistence 계약을 만들지 않는다. + - auth, 권한, 감사 로그, durable 운영 정책을 완성하지 않는다. + - runner production end-to-end 운영 정책이나 배포 파이프라인을 완성하지 않는다. + +## Source of Truth + +| 영역 | 기준 | 메모 | +|------|------|------| +| Roadmap | `agent-roadmap/phase/control-plane-product-surface/milestones/control-plane-operator-actions.md` | Milestone Task, SDD gate, 완료 evidence 반영 기준 | +| Code | `services/core/internal/httpserver/routes.go` | 기존 Core HTTP route가 action endpoint source of truth다. | +| Code | `services/core/internal/httpserver/job_handlers.go` | `POST /api/v1/jobs`, `POST /api/v1/jobs/{id}/executions` 입력/상태 응답 기준 | +| Code | `services/core/internal/httpserver/runner_cicd_handlers.go` | runner-scoped cancel/report/log/artifact/self-update/status action 기준 | +| Code | `services/core/internal/httpserver/execution_handlers.go` | execution-scoped log/artifact append와 readback 기준 | +| Code | `proto/oto/runner.proto` | runner-scoped request/response JSON body의 field source of truth | +| Code | `apps/client/lib/src/app/core_connection_client.dart` | `OtoConsoleConfig.serverHttpUrl`과 client adapter 패턴의 source of truth | +| Code | `packages/flutter/oto_console/lib/src/oto_console_contract.dart` | UI package의 transport-free state/view contract 기준 | +| External Provider | 없음 | 외부 provider mutation은 없다. | +| User Decision | 없음 | 기존 Core 계약 소비와 conservative UX 표준선으로 결정 가능하다. | + +## State Machine + +| 상태 | 진입 조건 | 다음 상태 | 근거 | +|------|-----------|-----------|------| +| idle | 화면이 action 가능한 read state를 렌더링한다. | confirming, disabled | `OtoSurfaceSnapshot.data`, 대상 id 존재 여부 | +| disabled | 대상 id, 필수 입력, Core 연결, SDD에서 허용한 endpoint 조건이 부족하다. | idle | UI validation, `OtoConsoleConfig.serverHttpUrl` | +| confirming | job create, cancel, self-update처럼 lifecycle-changing action을 사용자가 시작한다. | submitting, idle | action surface confirmation UI | +| submitting | adapter가 기존 Core write endpoint에 단일 in-flight 요청을 보낸다. | succeeded, deferred, failed | `apps/client` write adapter result | +| succeeded | Core가 HTTP 2xx와 accepted/success 또는 created payload를 반환한다. | refreshing, idle | `writeResponse`, proto response `accepted`/`success`, job DTO | +| deferred | self-update처럼 Core가 HTTP 200이지만 `deferred=true`를 반환한다. | refreshing, idle | `selfUpdateToJSON(... deferred=true ...)` | +| failed | Core가 4xx/5xx, invalid JSON, timeout, mismatch, terminal-state rejection을 반환한다. | idle, confirming | Core error response, adapter error state | +| refreshing | action 이후 read surface를 다시 조회한다. | idle, failed | 기존 read adapter and surface snapshot | + +## Interface Contract + +- 계약 원문: 없음. 이 Milestone은 tracked code/proto의 기존 endpoint와 DTO를 그대로 소비하며, SDD에 별도 agent-contract 원문을 복제하지 않는다. +- 입력: + - `serverHttpUrl`: `OtoConsoleConfig.serverHttpUrl`이며 모든 action adapter의 HTTP base URL이다. + - `POST /api/v1/jobs`: body `id`, `name`, optional `run_request`; `run_request`는 기존 `RunRequest` JSON field를 따른다. + - `POST /api/v1/jobs/{jobID}/executions`: body `id`; execution 생성 action을 노출하는 경우 기존 handler의 `id` 필드를 따른다. + - `POST /api/v1/runners/{runnerID}/executions/{executionID}/cancel`: body `runner_id`, `execution_id`, optional `reason`; path/body id mismatch는 error로 표시한다. + - `POST /api/v1/runners/{runnerID}/executions/{executionID}/report`: body `runner_id`, `job_id`, `execution_id`, `success`, `exit_code`, `message`, `step_events`; 운영자 UI에서 직접 report를 노출할 때는 destructive/system action으로 분리 표시한다. + - `POST /api/v1/runners/{runnerID}/executions/{executionID}/logs`: body `runner_id`, `execution_id`, `line`. + - `POST /api/v1/runners/{runnerID}/executions/{executionID}/artifacts`: body `runner_id`, `execution_id`, `name`, `path`. + - `POST /api/v1/runners/{runnerID}/self-update`: body `runner_id`, `version`, `download_url`; `download_url`은 Core 기준으로 HTTPS만 허용한다. +- 출력: + - `POST /api/v1/jobs`: HTTP 201이면 created job DTO를 success state로 변환한다. HTTP 400/409는 user-visible error state로 변환한다. + - execution create/report/log/artifact/cancel: HTTP 2xx와 `accepted` 또는 `success`를 success state로 변환한다. `error_message` 또는 `error`가 있으면 error state로 표시한다. + - self-update: `accepted=true`는 success, `deferred=true`는 deferred, `success=false`는 error state로 표시한다. + - action 완료 후 read surface는 기존 read adapter로 refresh한다. +- 금지: + - SDD 범위 안에서 신규 Core route, proto field, DB/persistence schema를 추가하지 않는다. + - client가 optimistic terminal state를 Core source of truth보다 먼저 확정하지 않는다. + - 동일 target/action에 대해 중복 in-flight request를 허용하지 않는다. + - failed action을 자동 무한 재시도하지 않는다. 사용자가 다시 실행해야 한다. + - credential, private release URL, enrollment token 원문을 tracked 문서나 UI 테스트 fixture에 남기지 않는다. + +## Acceptance Scenarios + +| ID | Milestone Task | Given | When | Then | +|----|----------------|-------|------|------| +| S01 | `action-sdd` | 현재 Milestone이 SDD 필요/잠금 상태다. | SDD gate를 확인한다. | SDD는 `[승인됨]`, SDD 잠금 `해제`, 사용자 리뷰 없음이며 Evidence Map이 모든 Task와 연결된다. | +| S02 | `job-create` | 사용자가 `id`, `name`, optional `run_request`를 입력하고 Core base URL이 유효하다. | job create action을 실행한다. | adapter는 `POST /api/v1/jobs`를 호출하고, 201/400/409/timeout을 success/error 상태로 표시하며 read surface refresh를 트리거한다. | +| S03 | `execution-actions` | 실행 대상 `runnerID`, `executionID`, optional `jobID`가 화면 state에 있다. | cancel/report/log/artifact action 중 하나를 실행한다. | adapter는 기존 runner/execution write endpoint를 호출하고 mismatch, terminal rejection, not found, accepted 결과를 구분해 표시한다. | +| S04 | `execution-actions` | action 이후 read surface가 최신 Core 상태를 다시 조회할 수 있다. | write action이 success 또는 deferred로 끝난다. | UI는 기존 read adapter로 jobs/executions/logs/artifacts 상태를 refresh하고 stale optimistic state를 남기지 않는다. | +| S05 | `runner-actions` | 사용자가 runner self-update target version과 HTTPS download URL을 입력한다. | self-update action을 실행한다. | adapter는 `POST /api/v1/runners/{runnerID}/self-update`를 호출하고 accepted/deferred/error state를 화면에 표시한다. | + +## Evidence Map + +| Scenario | Required Evidence | `agent-task` 연결 | `Spec Completion` 기대 | +|----------|-------------------|------------------|---------------------------| +| S01 | SDD file exists, `[승인됨]`, SDD 잠금 `해제`, no `USER_REVIEW.md`, `git diff --check` | `agent-task/m-control-plane-operator-actions/...` | `Spec Completion`에 S01, `action-sdd`, SDD path, no-user-review evidence를 남긴다. | +| S02 | `cd apps/client && flutter test`, `cd packages/flutter/oto_console && flutter test`, job create adapter/widget tests, related `server_test.go` route coverage reference | `agent-task/m-control-plane-operator-actions/...` | `Spec Completion`에 S02, `job-create`, adapter request/response state evidence를 남긴다. | +| S03 | `cd apps/client && flutter test`, `cd packages/flutter/oto_console && flutter test`, cancel/report/log/artifact action state tests, `services/core/internal/httpserver/server_test.go` existing route tests | `agent-task/m-control-plane-operator-actions/...` | `Spec Completion`에 S03, `execution-actions`, error/success state evidence를 남긴다. | +| S04 | UI refresh test proving write success/deferred triggers read adapter reload without optimistic terminal state | `agent-task/m-control-plane-operator-actions/...` | `Spec Completion`에 S04, `execution-actions`, refresh behavior evidence를 남긴다. | +| S05 | `cd apps/client && flutter test`, `cd packages/flutter/oto_console && flutter test`, self-update accepted/deferred/error tests, `TestHandleRunnerSelfUpdate` reference | `agent-task/m-control-plane-operator-actions/...` | `Spec Completion`에 S05, `runner-actions`, accepted/deferred/error evidence를 남긴다. | + +## Cross-repo Dependencies + +- 없음 + +## Drift Check + +- [x] Milestone 기능 Task와 Acceptance Scenario가 일치한다. +- [x] Evidence Map이 plan/code-review/complete.log에서 검증 가능하다. +- [x] agent-contract를 쓰는 경우 SDD에 계약 원문을 복제하지 않았다. +- [x] 사용자 리뷰가 필요한 항목은 `USER_REVIEW.md`에만 남겼다. + +## 사용자 리뷰 이력 + +- 없음 + +## 작업 컨텍스트 + +- 표준선: 기존 `services/core/internal/httpserver/**` route/handler/proto 계약을 source of truth로 삼고, `apps/client`는 HTTP adapter와 action state 변환을 소유하며, `packages/flutter/oto_console`은 transport-free UI contract와 action surface state를 소유한다. +- 후속 SDD: auth, 권한, 감사 로그, durable 운영 정책 또는 신규 Core API/schema/proto가 필요해질 때 별도 SDD로 분리한다. diff --git a/agent-task/m-control-plane-operator-actions/01_job_create/CODE_REVIEW-local-G06.md b/agent-task/m-control-plane-operator-actions/01_job_create/CODE_REVIEW-local-G06.md new file mode 100644 index 0000000..a2dc6d4 --- /dev/null +++ b/agent-task/m-control-plane-operator-actions/01_job_create/CODE_REVIEW-local-G06.md @@ -0,0 +1,154 @@ + + +# Code Review Reference - JOB_CREATE + +> **[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 selected SDD decision or selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with linked evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Environment/secret/service blockers, generic scope changes, repeated failures, and evidence gaps that a follow-up agent can close 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 only SDD/Milestone lock decisions 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-18 +task=m-control-plane-operator-actions/01_job_create, plan=0, tag=JOB_CREATE + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/control-plane-product-surface/milestones/control-plane-operator-actions.md` +- Task ids: + - `job-create`: 기존 `POST /api/v1/jobs` 계약을 사용하는 job 생성 action adapter와 UI 상태를 연결한다. +- Completion mode: check-on-pass + +## Spec Targets + +- SDD: `agent-roadmap/sdd/control-plane-product-surface/control-plane-operator-actions/SDD.md` +- Acceptance scenarios: + - `S02`: task=`job-create`; evidence=`cd apps/client && flutter test`, `cd packages/flutter/oto_console && flutter test`, job create adapter/widget tests, related `server_test.go` route coverage reference +- Completion mode: spec-check-on-pass + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. +리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다. + +1. 판정을 append한다. +2. `CODE_REVIEW-local-G06.md` → `code_review_local_G06_N.log`, `PLAN-local-G06.md` → `plan_local_G06_M.log`로 아카이브한다. +3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-control-plane-operator-actions/01_job_create/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. `USER_REVIEW.md`가 연결된 SDD/Milestone 결정으로 완료/PASS 해소되면 code-review가 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log` 작성 후 archive 이동한다. +4. PASS이고 task group이 `m-control-plane-operator-actions`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다. +5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [JOB_CREATE-1] Add Core Job Create Write Adapter | [ ] | +| [JOB_CREATE-2] Add Console Job Create Action Contract And UI | [ ] | +| [JOB_CREATE-3] Wire Job Create Through OtoClientApp And Refresh | [ ] | + +## 구현 체크리스트 + +- [ ] `apps/client`에 Core job create write adapter와 result mapping을 추가하고 `cd apps/client && flutter test --plain-name "OtoHttpCoreWriteClient maps job create responses"`가 통과하게 한다. +- [ ] `packages/flutter/oto_console`에 job create action view contract와 UI 상태를 추가하고 `cd packages/flutter/oto_console && flutter test --plain-name "OtoJobsSurface renders job create action states"`가 통과하게 한다. +- [ ] `OtoClientApp`에서 job create action을 연결하고 성공 후 read surface refresh를 트리거하며 `cd apps/client && flutter test --plain-name "OtoClientApp wires job create action and refresh"`가 통과하게 한다. +- [ ] `cd apps/client && flutter test`와 `cd packages/flutter/oto_console && flutter test`를 실행한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. +> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다. + +- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다. +- [ ] active `CODE_REVIEW-*-G??.md`를 `code_review_local_G06_N.log`로 아카이브한다. +- [ ] active `PLAN-*-G??.md`를 `plan_local_G06_M.log`로 아카이브한다. +- [ ] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다. +- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. +- [ ] PASS이면 active task 디렉터리 `agent-task/m-control-plane-operator-actions/01_job_create/`를 `agent-task/archive/YYYY/MM/m-control-plane-operator-actions/01_job_create/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. +- [ ] PASS이고 task group이 `m-control-plane-operator-actions`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다. +- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-control-plane-operator-actions/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. +- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-local-G06.md`와 `CODE_REVIEW-local-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가 연결된 SDD/Milestone 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다. + +## 계획 대비 변경 사항 + +_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._ + +## 주요 설계 결정 + +_구현 에이전트가 주요 설계 결정 사항을 기록한다._ + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 SDD 결정 또는 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 연결 대상: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- `POST /api/v1/jobs` body가 Core handler의 `id`, `name`, optional `run_request`만 사용하며 새 Core 계약을 추가하지 않았는지 확인한다. +- job create success 후 returned job id가 read refresh 대상에 포함되고 optimistic terminal state를 남기지 않는지 확인한다. +- submitting 중 중복 요청이 차단되고 error 상태가 사용자에게 표시되는지 확인한다. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +필수 규칙: +- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다. +- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다. +- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다. +- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다. +- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다. + +### JOB_CREATE-1 중간 검증 +``` +$ cd apps/client && flutter test --plain-name "OtoHttpCoreWriteClient maps job create responses" +(output) +``` + +### JOB_CREATE-2 중간 검증 +``` +$ cd packages/flutter/oto_console && flutter test --plain-name "OtoJobsSurface renders job create action states" +(output) +``` + +### JOB_CREATE-3 중간 검증 +``` +$ cd apps/client && flutter test --plain-name "OtoClientApp wires job create action and refresh" +(output) +``` + +### 최종 검증 +``` +$ cd apps/client && flutter test +(output) + +$ cd packages/flutter/oto_console && flutter test +(output) + +$ git diff --check +(output) +``` + +--- + +> **[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. diff --git a/agent-task/m-control-plane-operator-actions/01_job_create/PLAN-local-G06.md b/agent-task/m-control-plane-operator-actions/01_job_create/PLAN-local-G06.md new file mode 100644 index 0000000..b1da625 --- /dev/null +++ b/agent-task/m-control-plane-operator-actions/01_job_create/PLAN-local-G06.md @@ -0,0 +1,333 @@ + + +# Plan - JOB_CREATE + +## 이 파일을 읽는 구현 에이전트에게 + +구현의 마지막 단계는 반드시 active `CODE_REVIEW-local-G06.md`에서 구현 에이전트 소유 섹션을 실제 변경 내용, 설계 결정, 검증 출력으로 채우는 것이다. 검증 명령을 실행하고 실제 stdout/stderr를 붙인 뒤 active 파일을 그대로 두고 리뷰 준비를 보고한다. 최종 판정, log rename, `complete.log` 작성, archive 이동은 code-review-skill 전용이다. + +선택된 SDD 결정 또는 선택된 Milestone `구현 잠금 > 결정 필요` 항목 때문에 구현이 막히면 active review stub의 `사용자 리뷰 요청` 섹션에 정확한 연결 대상, 증거, 실행한 명령, 재개 조건을 기록하고 멈춘다. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 환경/secret/service 차단, 일반 범위 조정, 검증 증거 공백처럼 후속 agent가 닫을 수 있는 문제는 사용자 리뷰 요청이 아니라 `검증 결과` 또는 `계획 대비 변경 사항`에 기록한다. + +## 배경 + +현재 Milestone은 read-only Control Plane 위에 기존 Core write endpoint를 연결하는 단계다. `job-create`는 남은 ops-actions 작업 중 공통 write adapter와 action state 패턴을 처음 세우는 작업이므로, 이후 execution/runner action plan의 선행 조건이 된다. Core에는 이미 `POST /api/v1/jobs`가 있으므로 새 Core API/schema/proto 없이 Flutter adapter와 콘솔 action surface만 추가한다. + +## 사용자 리뷰 요청 흐름 + +구현 중 사용자 리뷰가 필요한 경우는 선택된 SDD 결정 또는 선택된 Milestone lock 결정이 실구현을 차단할 때뿐이다. 구현 에이전트는 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`에서 복사된 active review stub의 `사용자 리뷰 요청` 섹션만 채우고 멈춘다. 직접 사용자 프롬프트는 금지되며, code-review가 요청 타당성 검증과 실제 `USER_REVIEW.md` 작성을 소유한다. + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/control-plane-product-surface/milestones/control-plane-operator-actions.md` +- Task ids: + - `job-create`: 기존 `POST /api/v1/jobs` 계약을 사용하는 job 생성 action adapter와 UI 상태를 연결한다. +- Completion mode: check-on-pass + +## Spec Targets + +- SDD: `agent-roadmap/sdd/control-plane-product-surface/control-plane-operator-actions/SDD.md` +- Acceptance scenarios: + - `S02`: task=`job-create`; evidence=`cd apps/client && flutter test`, `cd packages/flutter/oto_console && flutter test`, job create adapter/widget tests, related `server_test.go` route coverage reference +- Completion mode: spec-check-on-pass + +## 분석 결과 + +### 읽은 파일 + +- `agent-ops/skills/common/plan/SKILL.md` +- `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` +- `agent-test/local/rules.md` +- `agent-roadmap/phase/control-plane-product-surface/milestones/control-plane-operator-actions.md` +- `agent-roadmap/sdd/control-plane-product-surface/control-plane-operator-actions/SDD.md` +- `apps/client/pubspec.yaml` +- `apps/client/lib/src/app/core_connection_client.dart` +- `apps/client/lib/src/app/oto_client_app.dart` +- `apps/client/test/widget_test.dart` +- `packages/flutter/oto_console/pubspec.yaml` +- `packages/flutter/oto_console/lib/src/oto_console_contract.dart` +- `packages/flutter/oto_console/lib/src/oto_console_shell.dart` +- `packages/flutter/oto_console/lib/src/oto_jobs_surface.dart` +- `packages/flutter/oto_console/lib/src/oto_executions_surface.dart` +- `packages/flutter/oto_console/lib/src/oto_runners_surface.dart` +- `packages/flutter/oto_console/lib/src/oto_artifacts_surface.dart` +- `packages/flutter/oto_console/lib/oto_console.dart` +- `packages/flutter/oto_console/test/oto_console_test.dart` +- `services/core/internal/httpserver/routes.go` +- `services/core/internal/httpserver/job_handlers.go` +- `services/core/internal/httpserver/execution_handlers.go` +- `services/core/internal/httpserver/runner_cicd_handlers.go` +- `services/core/internal/httpserver/dto.go` +- `proto/oto/runner.proto` + +### 테스트 환경 규칙 + +- 선택한 test_env: `local`. +- `agent-test/local/rules.md`가 존재하며 전체를 읽었다. +- 매칭 profile: `apps/client`, `packages/flutter/oto_console`, `services/core`에 대응하는 `agent-test/local/*project-smoke.md`는 없었다. `agent-test/local/rules.md`의 Monorepo Targets가 `apps/client`와 `packages/flutter/oto_console` 명령을 직접 제공하므로 `create-test`는 수행하지 않는다. +- 적용 명령: `cd apps/client && flutter test`, `cd packages/flutter/oto_console && flutter test`. +- 구현 범위가 Core API 추가를 금지하므로 `cd services/core && go test ./...`는 최종 필수 명령에서 제외한다. Core route는 기존 source of truth로만 참조한다. +- `<확인 필요>` 값은 없었다. 로컬 규칙의 기본 local 환경은 원격 runner지만, plan의 검증 명령은 repo layout 기준으로 재실행 가능한 패키지 명령이다. + +### 테스트 커버리지 공백 + +- `OtoHttpCoreReadClient` read mapping은 `apps/client/test/widget_test.dart`에서 다루지만, `POST /api/v1/jobs` write adapter, 201/400/409/timeout mapping은 없다. +- `OtoJobsSurface`는 loading/empty/error/data와 execution tap만 검증하며, job create form, disabled/submitting/success/error 상태는 없다. +- `OtoClientApp`은 read refresh만 검증하며, job create 후 returned job id를 read surface refresh 대상으로 합치는 테스트가 없다. + +### 심볼 참조 + +- renamed/removed symbol: none. +- 새로 추가될 symbol 후보: `OtoCoreWriteState`, `OtoCoreWriteResult`, `OtoCoreWriteClient`, `OtoHttpCoreWriteClient`, `OtoJobCreateDraft`, `OtoJobActionState`. 기존 call site는 변경 대신 optional constructor parameter와 callback 추가로 호환성을 유지한다. + +### 분할 판단 + +- split decision policy를 plan file 선택 전에 평가했다. +- shared task group: `agent-task/m-control-plane-operator-actions/`. +- sibling plans: + - `01_job_create`: 선행 없음. 공통 write adapter/result 패턴과 job create UI를 만든다. + - `02+01_execution_actions`: predecessor `01_job_create`. `01_job_create/complete.log`가 아직 없으므로 구현은 01 PASS 이후 시작해야 한다. + - `03+01_runner_actions`: predecessor `01_job_create`. `01_job_create/complete.log`가 아직 없으므로 구현은 01 PASS 이후 시작해야 한다. +- 이 plan은 foundation과 첫 call site가 결합된 단위다. adapter만 분리하면 검증할 사용자 action state가 부족하고, job create UI까지 포함해야 S02 evidence가 닫힌다. + +### 범위 결정 근거 + +- `services/core/internal/httpserver/**`, `proto/oto/runner.proto`는 변경하지 않는다. SDD가 신규 Core API/schema/proto를 범위 제외로 고정했고, `POST /api/v1/jobs`는 이미 존재한다. +- auth, 권한, 감사 로그, durable persistence UI, runner production 운영 정책은 Milestone 범위 제외다. +- execution/runner write action은 sibling plan에서 처리한다. 이 plan은 공통 write result/client가 필요하면 확장하되 completion target은 `job-create`와 S02에 한정한다. +- 새 package는 추가하지 않는다. `apps/client`에는 이미 `http`가 있고, `oto_console`은 transport-free contract/UI만 유지한다. + +### 빌드 등급 + +- 결정: `local-G06`. +- 근거: bounded Flutter adapter/UI/test 작업이며 기존 Core 계약과 deterministic widget/unit tests로 검증 가능하지만, 공통 write contract와 app refresh state를 새로 세우므로 중간 수준 이상의 call-site 영향이 있다. + +## 구현 체크리스트 + +- [ ] `apps/client`에 Core job create write adapter와 result mapping을 추가하고 `cd apps/client && flutter test --plain-name "OtoHttpCoreWriteClient maps job create responses"`가 통과하게 한다. +- [ ] `packages/flutter/oto_console`에 job create action view contract와 UI 상태를 추가하고 `cd packages/flutter/oto_console && flutter test --plain-name "OtoJobsSurface renders job create action states"`가 통과하게 한다. +- [ ] `OtoClientApp`에서 job create action을 연결하고 성공 후 read surface refresh를 트리거하며 `cd apps/client && flutter test --plain-name "OtoClientApp wires job create action and refresh"`가 통과하게 한다. +- [ ] `cd apps/client && flutter test`와 `cd packages/flutter/oto_console && flutter test`를 실행한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +### [JOB_CREATE-1] Add Core Job Create Write Adapter + +문제: `apps/client/lib/src/app/core_connection_client.dart:100-177`에는 read state/result/client만 있고 write action 결과가 없다. `_getObject`는 `GET` 전용이며 `POST /api/v1/jobs`의 201/400/409/timeout을 action state로 표현할 수 없다. + +해결 방법: read result와 유사한 write result 타입을 추가하고, `OtoCoreWriteClient.createJob`과 `OtoHttpCoreWriteClient`를 같은 파일에 둔다. 요청 body는 Core handler 기준 `id`, `name`, optional `run_request`만 보낸다. 성공은 HTTP 201 + job DTO, 400/409/invalid JSON/timeout은 user-visible error로 변환한다. + +Before: + +```dart +// apps/client/lib/src/app/core_connection_client.dart:100-177 +enum OtoCoreReadState { loading, empty, data, error } + +class OtoCoreReadResult { + ... +} + +abstract class OtoCoreReadClient { + Future> fetchRunner(...); + ... +} +``` + +After: + +```dart +enum OtoCoreWriteState { idle, submitting, succeeded, deferred, failed } + +class OtoCoreWriteResult { + final OtoCoreWriteState state; + final T? data; + final String message; + final int? statusCode; + ... +} + +abstract class OtoCoreWriteClient { + Future> createJob( + OtoConsoleConfig config, + OtoJobCreateDraft draft, + ); +} + +class OtoHttpCoreWriteClient implements OtoCoreWriteClient { + @override + Future> createJob(...) async { + final response = await _httpClient + .post( + _endpointUri(config.serverHttpUrl, '/api/v1/jobs'), + headers: const {'content-type': 'application/json'}, + body: jsonEncode(draft.toJson()), + ) + .timeout(timeout); + ... + } +} +``` + +수정 파일 및 체크리스트: + +- [ ] `apps/client/lib/src/app/core_connection_client.dart`: write enum/result/client와 `OtoHttpCoreWriteClient.createJob` 추가. +- [ ] `apps/client/lib/src/app/core_connection_client.dart`: `_endpointUri`를 재사용하고 URL validation error를 write error로 변환. +- [ ] `apps/client/test/widget_test.dart`: `MockClient`로 201/400/409/timeout/invalid config mapping 테스트 추가. + +테스트 작성: + +- 작성: `apps/client/test/widget_test.dart` +- 테스트명: `OtoHttpCoreWriteClient maps job create responses` +- assertion goal: `/api/v1/jobs` POST path, JSON body, 201 data, 400/409 error message/statusCode, invalid base URL error를 검증한다. +- fixture: `MockClient`, `OtoConsoleConfig(serverHttpUrl: 'http://core.example.test:18020', ...)`, created job JSON. + +중간 검증: + +```bash +cd apps/client && flutter test --plain-name "OtoHttpCoreWriteClient maps job create responses" +``` + +예상 결과: 새 adapter test가 통과한다. + +### [JOB_CREATE-2] Add Console Job Create Action Contract And UI + +문제: `packages/flutter/oto_console/lib/src/oto_console_contract.dart:136-199`는 generic surface snapshot과 job view model만 제공하며 action view state가 없다. `packages/flutter/oto_console/lib/src/oto_jobs_surface.dart:4-14`는 execution tap callback만 받고, `57-71`의 content switch는 create action UI를 렌더링하지 않는다. + +해결 방법: `oto_console_contract.dart`에 transport-free `OtoJobCreateDraft`, `OtoActionStatus`, `OtoActionViewState` 같은 작은 contract를 추가한다. `OtoJobsSurface`는 optional `jobCreateState`와 `onCreateJob` callback을 받고, header 아래에 compact form을 렌더링한다. id/name이 비어 있거나 submitting이면 submit을 disable하고, success/error message를 inline으로 표시한다. + +Before: + +```dart +// packages/flutter/oto_console/lib/src/oto_jobs_surface.dart:4-14 +class OtoJobsSurface extends StatelessWidget { + final OtoSurfaceSnapshot> snapshot; + final OtoConsoleThemeAdapter? themeAdapter; + final ValueChanged? onExecutionTap; + + const OtoJobsSurface({ + super.key, + required this.snapshot, + this.themeAdapter, + this.onExecutionTap, + }); +``` + +After: + +```dart +class OtoJobsSurface extends StatelessWidget { + final OtoSurfaceSnapshot> snapshot; + final OtoActionViewState? jobCreateState; + final ValueChanged? onCreateJob; + ... +} +``` + +수정 파일 및 체크리스트: + +- [ ] `packages/flutter/oto_console/lib/src/oto_console_contract.dart`: action status/state와 job create draft 추가. +- [ ] `packages/flutter/oto_console/lib/src/oto_jobs_surface.dart`: form/controller state를 가진 내부 widget 또는 stateful surface로 변경. +- [ ] `packages/flutter/oto_console/test/oto_console_test.dart`: disabled/submitting/success/error callback tests 추가. +- [ ] `packages/flutter/oto_console/lib/oto_console.dart`: 새 contract가 existing export를 통해 노출되는지 확인. 별도 export 추가가 필요하면 최소 변경. + +테스트 작성: + +- 작성: `packages/flutter/oto_console/test/oto_console_test.dart` +- 테스트명: `OtoJobsSurface renders job create action states` +- assertion goal: empty id/name submit disabled, valid id/name callback payload, submitting disables button, error/success text visible. +- fixture: `OtoJobsSurface(snapshot: OtoSurfaceSnapshot.empty(), onCreateJob: ...)`. + +중간 검증: + +```bash +cd packages/flutter/oto_console && flutter test --plain-name "OtoJobsSurface renders job create action states" +``` + +예상 결과: job create surface widget test가 통과한다. + +### [JOB_CREATE-3] Wire Job Create Through OtoClientApp And Refresh + +문제: `apps/client/lib/src/app/oto_client_app.dart:31-70`에는 `readClient`만 있고 write client injection point가 없다. `_refreshCoreStatus`는 `widget.jobIDs`만 조회하므로 `POST /api/v1/jobs` 성공 후 생성된 job id가 refresh 대상에 포함되지 않는다. + +해결 방법: `OtoClientApp`에 optional `writeClient`와 default `OtoHttpCoreWriteClient`를 추가한다. `_createdJobIDs`를 local state로 유지하고 `_fetchJobs`는 `widget.jobIDs + _createdJobIDs`를 dedupe해 조회한다. `_submitJobCreate`는 single in-flight guard를 적용하고, 성공 시 returned job id를 `_createdJobIDs`에 추가한 뒤 `_refreshCoreStatus`를 호출한다. 실패는 optimistic state 없이 `OtoActionViewState.failed`로 표시한다. + +Before: + +```dart +// apps/client/lib/src/app/oto_client_app.dart:31-70 +class OtoClientApp extends StatefulWidget { + final OtoCoreConnectionClient? coreClient; + final OtoCoreReadClient? readClient; + ... +} + +class _OtoClientAppState extends State { + late final OtoCoreConnectionClient _defaultCoreClient = + OtoHttpCoreConnectionClient(); + late final OtoCoreReadClient _defaultReadClient = OtoHttpCoreReadClient(); +``` + +After: + +```dart +class OtoClientApp extends StatefulWidget { + final OtoCoreWriteClient? writeClient; + ... +} + +class _OtoClientAppState extends State { + late final OtoCoreWriteClient _defaultWriteClient = OtoHttpCoreWriteClient(); + final Set _createdJobIDs = {}; + OtoActionViewState _jobCreateState = const OtoActionViewState.idle(); + ... +} +``` + +수정 파일 및 체크리스트: + +- [ ] `apps/client/lib/src/app/oto_client_app.dart`: `writeClient` injection, `_createdJobIDs`, `_jobCreateState`, `_submitJobCreate` 추가. +- [ ] `apps/client/lib/src/app/oto_client_app.dart`: `OtoJobsSurface`에 `jobCreateState`와 `onCreateJob` 연결. +- [ ] `apps/client/test/widget_test.dart`: fake write client 추가, success/error/submitting refresh tests 추가. + +테스트 작성: + +- 작성: `apps/client/test/widget_test.dart` +- 테스트명: `OtoClientApp wires job create action and refresh` +- assertion goal: create action calls fake write client, success returned id is fetched via read client, error text is visible, duplicate submit while submitting does not call write twice. +- fixture: `_FakeCoreWriteClient`, `_FakeCoreReadClient` with `onFetchJob`. + +중간 검증: + +```bash +cd apps/client && flutter test --plain-name "OtoClientApp wires job create action and refresh" +``` + +예상 결과: app-level job create wiring test가 통과한다. + +## 의존 관계 및 구현 순서 + +1. `JOB_CREATE-1`에서 write adapter/result를 먼저 만든다. +2. `JOB_CREATE-2`에서 console contract/UI를 추가한다. +3. `JOB_CREATE-3`에서 app wiring과 refresh behavior를 연결한다. + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `apps/client/lib/src/app/core_connection_client.dart` | JOB_CREATE-1 | +| `apps/client/lib/src/app/oto_client_app.dart` | JOB_CREATE-3 | +| `apps/client/test/widget_test.dart` | JOB_CREATE-1, JOB_CREATE-3 | +| `packages/flutter/oto_console/lib/src/oto_console_contract.dart` | JOB_CREATE-2 | +| `packages/flutter/oto_console/lib/src/oto_jobs_surface.dart` | JOB_CREATE-2 | +| `packages/flutter/oto_console/lib/oto_console.dart` | JOB_CREATE-2 | +| `packages/flutter/oto_console/test/oto_console_test.dart` | JOB_CREATE-2 | + +## 최종 검증 + +```bash +cd apps/client && flutter test +cd packages/flutter/oto_console && flutter test +git diff --check +``` + +예상 결과: 모든 명령이 exit 0이다. Flutter/Go test cache 여부는 해당 없음이며, widget/unit test는 fresh process로 실행된다. + +모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/agent-task/m-control-plane-operator-actions/02+01_execution_actions/CODE_REVIEW-local-G07.md b/agent-task/m-control-plane-operator-actions/02+01_execution_actions/CODE_REVIEW-local-G07.md new file mode 100644 index 0000000..7a6d1a6 --- /dev/null +++ b/agent-task/m-control-plane-operator-actions/02+01_execution_actions/CODE_REVIEW-local-G07.md @@ -0,0 +1,155 @@ + + +# Code Review Reference - EXEC_ACTIONS + +> **[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 selected SDD decision or selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with linked evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Environment/secret/service blockers, generic scope changes, repeated failures, and evidence gaps that a follow-up agent can close 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 only SDD/Milestone lock decisions 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-18 +task=m-control-plane-operator-actions/02+01_execution_actions, plan=0, tag=EXEC_ACTIONS + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/control-plane-product-surface/milestones/control-plane-operator-actions.md` +- Task ids: + - `execution-actions`: 기존 execution/runner write endpoint를 사용하는 취소, report/log/artifact action 진입 상태를 연결한다. +- Completion mode: check-on-pass + +## Spec Targets + +- SDD: `agent-roadmap/sdd/control-plane-product-surface/control-plane-operator-actions/SDD.md` +- Acceptance scenarios: + - `S03`: task=`execution-actions`; evidence=`cd apps/client && flutter test`, `cd packages/flutter/oto_console && flutter test`, cancel/report/log/artifact action state tests, `services/core/internal/httpserver/server_test.go` existing route tests + - `S04`: task=`execution-actions`; evidence=UI refresh test proving write success/deferred triggers read adapter reload without optimistic terminal state +- Completion mode: spec-check-on-pass + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. +리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다. + +1. 판정을 append한다. +2. `CODE_REVIEW-local-G07.md` → `code_review_local_G07_N.log`, `PLAN-local-G07.md` → `plan_local_G07_M.log`로 아카이브한다. +3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-control-plane-operator-actions/02+01_execution_actions/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. +4. PASS이고 task group이 `m-control-plane-operator-actions`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다. +5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [EXEC_ACTIONS-1] Extend Write Adapter For Execution Actions | [ ] | +| [EXEC_ACTIONS-2] Add Execution Action Entry State To Console Surface | [ ] | +| [EXEC_ACTIONS-3] Wire Execution Actions Through OtoClientApp Refresh | [ ] | + +## 구현 체크리스트 + +- [ ] `01_job_create` predecessor `complete.log`를 확인한 뒤 해당 write adapter/result 패턴을 확장하고 `cd apps/client && flutter test --plain-name "OtoHttpCoreWriteClient maps execution action responses"`가 통과하게 한다. +- [ ] `packages/flutter/oto_console`에 execution cancel/report/log/artifact action entry state를 추가하고 `cd packages/flutter/oto_console && flutter test --plain-name "OtoExecutionsSurface renders execution action states"`가 통과하게 한다. +- [ ] `OtoClientApp`에서 execution action success/deferred 이후 read/detail refresh를 연결하고 `cd apps/client && flutter test --plain-name "OtoClientApp wires execution actions and refresh"`가 통과하게 한다. +- [ ] `cd apps/client && flutter test`와 `cd packages/flutter/oto_console && flutter test`를 실행한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. +> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다. + +- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다. +- [ ] active `CODE_REVIEW-*-G??.md`를 `code_review_local_G07_N.log`로 아카이브한다. +- [ ] active `PLAN-*-G??.md`를 `plan_local_G07_M.log`로 아카이브한다. +- [ ] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다. +- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. +- [ ] PASS이면 active task 디렉터리 `agent-task/m-control-plane-operator-actions/02+01_execution_actions/`를 `agent-task/archive/YYYY/MM/m-control-plane-operator-actions/02+01_execution_actions/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. +- [ ] PASS이고 task group이 `m-control-plane-operator-actions`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다. +- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-control-plane-operator-actions/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. +- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-local-G07.md`와 `CODE_REVIEW-local-G07.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가 연결된 SDD/Milestone 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다. + +## 계획 대비 변경 사항 + +_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._ + +## 주요 설계 결정 + +_구현 에이전트가 주요 설계 결정 사항을 기록한다._ + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 SDD 결정 또는 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 연결 대상: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- `01_job_create`의 completed write result/client 패턴을 재사용했는지 확인한다. +- cancel/report/log/artifact가 기존 runner/execution endpoint field만 사용하고 새 Core contract를 만들지 않았는지 확인한다. +- write success 후 jobs/executions/logs/artifacts refresh가 실행되고 stale optimistic state가 남지 않는지 확인한다. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +필수 규칙: +- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다. +- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다. +- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다. +- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다. +- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다. + +### EXEC_ACTIONS-1 중간 검증 +``` +$ cd apps/client && flutter test --plain-name "OtoHttpCoreWriteClient maps execution action responses" +(output) +``` + +### EXEC_ACTIONS-2 중간 검증 +``` +$ cd packages/flutter/oto_console && flutter test --plain-name "OtoExecutionsSurface renders execution action states" +(output) +``` + +### EXEC_ACTIONS-3 중간 검증 +``` +$ cd apps/client && flutter test --plain-name "OtoClientApp wires execution actions and refresh" +(output) +``` + +### 최종 검증 +``` +$ cd apps/client && flutter test +(output) + +$ cd packages/flutter/oto_console && flutter test +(output) + +$ git diff --check +(output) +``` + +--- + +> **[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. diff --git a/agent-task/m-control-plane-operator-actions/02+01_execution_actions/PLAN-local-G07.md b/agent-task/m-control-plane-operator-actions/02+01_execution_actions/PLAN-local-G07.md new file mode 100644 index 0000000..ae2bd14 --- /dev/null +++ b/agent-task/m-control-plane-operator-actions/02+01_execution_actions/PLAN-local-G07.md @@ -0,0 +1,297 @@ + + +# Plan - EXEC_ACTIONS + +## 이 파일을 읽는 구현 에이전트에게 + +구현의 마지막 단계는 반드시 active `CODE_REVIEW-local-G07.md`에서 구현 에이전트 소유 섹션을 실제 변경 내용, 설계 결정, 검증 출력으로 채우는 것이다. 검증 명령을 실행하고 실제 stdout/stderr를 붙인 뒤 active 파일을 그대로 두고 리뷰 준비를 보고한다. 최종 판정, log rename, `complete.log` 작성, archive 이동은 code-review-skill 전용이다. + +선택된 SDD 결정 또는 선택된 Milestone `구현 잠금 > 결정 필요` 항목 때문에 구현이 막히면 active review stub의 `사용자 리뷰 요청` 섹션에 정확한 연결 대상, 증거, 실행한 명령, 재개 조건을 기록하고 멈춘다. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 환경/secret/service 차단, 일반 범위 조정, 검증 증거 공백처럼 후속 agent가 닫을 수 있는 문제는 사용자 리뷰 요청이 아니라 `검증 결과` 또는 `계획 대비 변경 사항`에 기록한다. + +## 배경 + +`execution-actions`는 실행 취소, report, log, artifact write 진입점을 기존 runner/execution endpoint에 연결한다. 이 작업은 `01_job_create`에서 만든 write result/client/action state 패턴을 재사용해야 하므로 split dependency가 있다. Core route와 proto DTO는 이미 존재하므로 UI는 action entry state와 refresh behavior에 집중한다. + +## 사용자 리뷰 요청 흐름 + +구현 중 사용자 리뷰가 필요한 경우는 선택된 SDD 결정 또는 선택된 Milestone lock 결정이 실구현을 차단할 때뿐이다. 구현 에이전트는 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`에서 복사된 active review stub의 `사용자 리뷰 요청` 섹션만 채우고 멈춘다. 직접 사용자 프롬프트는 금지되며, code-review가 요청 타당성 검증과 실제 `USER_REVIEW.md` 작성을 소유한다. + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/control-plane-product-surface/milestones/control-plane-operator-actions.md` +- Task ids: + - `execution-actions`: 기존 execution/runner write endpoint를 사용하는 취소, report/log/artifact action 진입 상태를 연결한다. +- Completion mode: check-on-pass + +## Spec Targets + +- SDD: `agent-roadmap/sdd/control-plane-product-surface/control-plane-operator-actions/SDD.md` +- Acceptance scenarios: + - `S03`: task=`execution-actions`; evidence=`cd apps/client && flutter test`, `cd packages/flutter/oto_console && flutter test`, cancel/report/log/artifact action state tests, `services/core/internal/httpserver/server_test.go` existing route tests + - `S04`: task=`execution-actions`; evidence=UI refresh test proving write success/deferred triggers read adapter reload without optimistic terminal state +- Completion mode: spec-check-on-pass + +## 분석 결과 + +### 읽은 파일 + +- `agent-ops/skills/common/plan/SKILL.md` +- `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` +- `agent-test/local/rules.md` +- `agent-roadmap/phase/control-plane-product-surface/milestones/control-plane-operator-actions.md` +- `agent-roadmap/sdd/control-plane-product-surface/control-plane-operator-actions/SDD.md` +- `apps/client/pubspec.yaml` +- `apps/client/lib/src/app/core_connection_client.dart` +- `apps/client/lib/src/app/oto_client_app.dart` +- `apps/client/test/widget_test.dart` +- `packages/flutter/oto_console/pubspec.yaml` +- `packages/flutter/oto_console/lib/src/oto_console_contract.dart` +- `packages/flutter/oto_console/lib/src/oto_console_shell.dart` +- `packages/flutter/oto_console/lib/src/oto_jobs_surface.dart` +- `packages/flutter/oto_console/lib/src/oto_executions_surface.dart` +- `packages/flutter/oto_console/lib/src/oto_runners_surface.dart` +- `packages/flutter/oto_console/lib/src/oto_artifacts_surface.dart` +- `packages/flutter/oto_console/lib/oto_console.dart` +- `packages/flutter/oto_console/test/oto_console_test.dart` +- `services/core/internal/httpserver/routes.go` +- `services/core/internal/httpserver/execution_handlers.go` +- `services/core/internal/httpserver/runner_cicd_handlers.go` +- `services/core/internal/httpserver/dto.go` +- `proto/oto/runner.proto` + +### 테스트 환경 규칙 + +- 선택한 test_env: `local`. +- `agent-test/local/rules.md`가 존재하며 전체를 읽었다. +- 매칭 profile: `apps/client`, `packages/flutter/oto_console`, `services/core`에 대응하는 `agent-test/local/*project-smoke.md`는 없었다. Monorepo Targets가 필요한 Flutter test 명령을 제공하므로 `create-test`는 수행하지 않는다. +- 적용 명령: `cd apps/client && flutter test`, `cd packages/flutter/oto_console && flutter test`. +- Core route/proto는 변경하지 않으므로 `cd services/core && go test ./...`는 필수 명령이 아니다. 기존 route behavior는 handler/proto source와 existing server tests를 참조한다. +- `<확인 필요>` 값은 없었다. + +### 테스트 커버리지 공백 + +- 기존 앱/콘솔 tests는 execution read/expand/log/artifact render만 검증한다. +- cancel/report/log/artifact write adapter request body, path/body mismatch error, accepted/success/error mapping tests가 없다. +- write success 후 `_refreshCoreStatus`와 expanded execution detail reload가 실행되어 stale optimistic state가 남지 않는 app-level test가 없다. + +### 심볼 참조 + +- renamed/removed symbol: none. +- 이 plan은 `01_job_create`가 추가한 write result/client/action state symbol을 확장한다. 구현 시작 전 `01_job_create/complete.log`와 실제 symbol 이름을 확인하고 plan의 후보 이름과 다르면 `계획 대비 변경 사항`에 기록한다. + +### 분할 판단 + +- split decision policy를 plan file 선택 전에 평가했다. +- shared task group: `agent-task/m-control-plane-operator-actions/`. +- sibling plans: + - `01_job_create`: 선행 foundation. active dir은 존재하지만 plan 작성 시점에 `complete.log`는 없다. + - `02+01_execution_actions`: predecessor index `01`. 구현은 `agent-task/m-control-plane-operator-actions/01_job_create/complete.log` 또는 matching archive `complete.log`가 생긴 뒤 시작한다. + - `03+01_runner_actions`: predecessor index `01`. +- predecessor status for this plan: missing at plan creation. runtime dependency는 디렉터리명 `02+01_execution_actions`가 source of truth다. + +### 범위 결정 근거 + +- `services/core/internal/httpserver/**`, `proto/oto/runner.proto`는 변경하지 않는다. 기존 endpoints는 `POST /api/v1/runners/{runnerID}/executions/{executionID}/cancel|report|logs|artifacts`와 `POST /api/v1/executions/{executionID}/logs|artifacts`다. +- 이 plan은 UI action 진입 상태와 adapter mapping만 다룬다. auth/audit/durable policy와 production runner orchestration은 제외한다. +- runner self-update/status action은 sibling `03+01_runner_actions` 범위다. +- 새 package는 추가하지 않는다. + +### 빌드 등급 + +- 결정: `local-G07`. +- 근거: bounded Flutter work지만 cancel/report/log/artifact 네 action과 refresh semantics가 결합되어 blast radius와 test matrix가 `job-create`보다 크다. + +## 구현 체크리스트 + +- [ ] `01_job_create` predecessor `complete.log`를 확인한 뒤 해당 write adapter/result 패턴을 확장하고 `cd apps/client && flutter test --plain-name "OtoHttpCoreWriteClient maps execution action responses"`가 통과하게 한다. +- [ ] `packages/flutter/oto_console`에 execution cancel/report/log/artifact action entry state를 추가하고 `cd packages/flutter/oto_console && flutter test --plain-name "OtoExecutionsSurface renders execution action states"`가 통과하게 한다. +- [ ] `OtoClientApp`에서 execution action success/deferred 이후 read/detail refresh를 연결하고 `cd apps/client && flutter test --plain-name "OtoClientApp wires execution actions and refresh"`가 통과하게 한다. +- [ ] `cd apps/client && flutter test`와 `cd packages/flutter/oto_console && flutter test`를 실행한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +### [EXEC_ACTIONS-1] Extend Write Adapter For Execution Actions + +문제: `apps/client/lib/src/app/core_connection_client.dart:147-177`는 read client뿐이고, 현재 write adapter가 없다. Core는 `runner_cicd_handlers.go`에서 cancel/report/log/artifact write 결과를 `success`, `accepted`, `error_message`로 반환하므로 job create와 다른 result parsing이 필요하다. + +해결 방법: `01_job_create`에서 만든 `OtoCoreWriteClient`에 execution action methods를 추가한다. Cancel은 `CancelRunRequest`, report는 `ExecutionReportRequest`, log는 `LogAppendRequest`, artifact는 `ArtifactReportRequest` JSON field를 따른다. HTTP 2xx라도 `accepted=false`, `success=false`, `error_message`가 있으면 failed state로 변환한다. + +Before: + +```dart +// apps/client/lib/src/app/core_connection_client.dart:147-177 +abstract class OtoCoreReadClient { + Future> fetchRunner(...); + ... + Future>> fetchArtifacts(...); +} +``` + +After: + +```dart +abstract class OtoCoreWriteClient { + Future> cancelExecution(...); + Future> reportExecution(...); + Future> appendExecutionLog(...); + Future> appendExecutionArtifact(...); +} +``` + +수정 파일 및 체크리스트: + +- [ ] `apps/client/lib/src/app/core_connection_client.dart`: execution action draft/request models 추가. +- [ ] `apps/client/lib/src/app/core_connection_client.dart`: runner-scoped path/body id를 모두 보내고 mismatch error를 failed state로 변환. +- [ ] `apps/client/test/widget_test.dart`: accepted/success/error/status code mapping tests 추가. + +테스트 작성: + +- 작성: `apps/client/test/widget_test.dart` +- 테스트명: `OtoHttpCoreWriteClient maps execution action responses` +- assertion goal: cancel/report/log/artifact path, JSON body, accepted/success, `error_message`, 400/404/timeout mapping. +- fixture: `MockClient` with runner id `runner-1`, execution id `exec-1`, job id `job-1`. + +중간 검증: + +```bash +cd apps/client && flutter test --plain-name "OtoHttpCoreWriteClient maps execution action responses" +``` + +예상 결과: execution write adapter tests가 통과한다. + +### [EXEC_ACTIONS-2] Add Execution Action Entry State To Console Surface + +문제: `packages/flutter/oto_console/lib/src/oto_executions_surface.dart:4-22`는 expand/read callbacks만 받고, `170-240`의 execution row에는 action controls가 없다. `OtoExecutionViewModel.runnerID`는 있지만 action availability/feedback state가 없다. + +해결 방법: console contract에 execution action state map 또는 typed `OtoExecutionActionState`를 추가한다. `OtoExecutionsSurface`는 expanded row 안에 cancel/report/log/artifact compact controls를 렌더링하고, runnerID/executionID가 없거나 terminal state이면 destructive action을 disable한다. Cancel은 confirmation을 요구하고, report/log/artifact는 명확한 entry state와 inline error/success를 표시한다. + +Before: + +```dart +// packages/flutter/oto_console/lib/src/oto_executions_surface.dart:4-22 +class OtoExecutionsSurface extends StatelessWidget { + final OtoSurfaceSnapshot> snapshot; + final String? expandedExecutionID; + ... + final ValueChanged? onExpandExecution; +} +``` + +After: + +```dart +class OtoExecutionsSurface extends StatelessWidget { + final Map actionStates; + final ValueChanged? onCancelExecution; + final ValueChanged? onReportExecution; + final ValueChanged? onAppendLog; + final ValueChanged? onAppendArtifact; + ... +} +``` + +수정 파일 및 체크리스트: + +- [ ] `packages/flutter/oto_console/lib/src/oto_console_contract.dart`: execution action drafts/state 추가. +- [ ] `packages/flutter/oto_console/lib/src/oto_executions_surface.dart`: expanded row에 action controls 추가. +- [ ] `packages/flutter/oto_console/test/oto_console_test.dart`: disabled, confirmation, submitting, success/error tests 추가. + +테스트 작성: + +- 작성: `packages/flutter/oto_console/test/oto_console_test.dart` +- 테스트명: `OtoExecutionsSurface renders execution action states` +- assertion goal: runnerID 없는 action disabled, cancel confirmation, submitting disables controls, success/error text visible, callbacks receive correct ids. +- fixture: `OtoExecutionViewModel(executionID: 'exec-1', runnerID: 'runner-1', ...)`. + +중간 검증: + +```bash +cd packages/flutter/oto_console && flutter test --plain-name "OtoExecutionsSurface renders execution action states" +``` + +예상 결과: execution surface action tests가 통과한다. + +### [EXEC_ACTIONS-3] Wire Execution Actions Through OtoClientApp Refresh + +문제: `apps/client/lib/src/app/oto_client_app.dart:116-177`의 refresh flow는 read-only이고, `542-553`의 `OtoExecutionsSurface` 연결은 expand/artifacts navigation만 전달한다. write success 이후 jobs/executions/logs/artifacts snapshot을 reload하는 경로가 없다. + +해결 방법: app state에 execution action state map을 추가하고, 각 action submit method가 write client를 호출하게 한다. success 또는 deferred에 해당하는 결과는 `_refreshCoreStatus()`를 호출하고 expanded execution이면 `_loadExecutionDetail(execID)`를 다시 호출한다. 실패는 snapshot을 임의로 바꾸지 않고 action state만 failed로 표시한다. + +Before: + +```dart +// apps/client/lib/src/app/oto_client_app.dart:542-553 +executions: OtoExecutionsSurface( + snapshot: _executionsSnapshot, + expandedExecutionID: _expandedExecutionID, + logsSnapshot: _logsSnapshot, + artifactsSnapshot: _expandedArtifactsSnapshot, + onExpandExecution: _loadExecutionDetail, + onViewArtifactsSection: () { ... }, + themeAdapter: themeAdapter, +), +``` + +After: + +```dart +executions: OtoExecutionsSurface( + snapshot: _executionsSnapshot, + actionStates: _executionActionStates, + onCancelExecution: _submitCancelExecution, + onReportExecution: _submitReportExecution, + onAppendLog: _submitAppendExecutionLog, + onAppendArtifact: _submitAppendExecutionArtifact, + ... +), +``` + +수정 파일 및 체크리스트: + +- [ ] `apps/client/lib/src/app/oto_client_app.dart`: action state map and submit methods 추가. +- [ ] `apps/client/lib/src/app/oto_client_app.dart`: `OtoExecutionsSurface` callbacks 연결. +- [ ] `apps/client/test/widget_test.dart`: fake write client methods와 refresh invocation assertions 추가. + +테스트 작성: + +- 작성: `apps/client/test/widget_test.dart` +- 테스트명: `OtoClientApp wires execution actions and refresh` +- assertion goal: action callback calls write client, success triggers fetchExecution/fetchLogs/fetchArtifacts refresh, failed action displays error and does not mutate read snapshot. +- fixture: `_FakeCoreWriteClient`, `_FakeCoreReadClient`. + +중간 검증: + +```bash +cd apps/client && flutter test --plain-name "OtoClientApp wires execution actions and refresh" +``` + +예상 결과: app execution action wiring test가 통과한다. + +## 의존 관계 및 구현 순서 + +1. 구현 시작 전에 predecessor `01_job_create`의 `complete.log`를 확인한다. 디렉터리명 `02+01_execution_actions`의 `+01`이 유일한 runtime dependency다. +2. `EXEC_ACTIONS-1`에서 completed write adapter pattern을 확장한다. +3. `EXEC_ACTIONS-2`에서 console surface action state를 추가한다. +4. `EXEC_ACTIONS-3`에서 app wiring과 refresh behavior를 연결한다. + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `apps/client/lib/src/app/core_connection_client.dart` | EXEC_ACTIONS-1 | +| `apps/client/lib/src/app/oto_client_app.dart` | EXEC_ACTIONS-3 | +| `apps/client/test/widget_test.dart` | EXEC_ACTIONS-1, EXEC_ACTIONS-3 | +| `packages/flutter/oto_console/lib/src/oto_console_contract.dart` | EXEC_ACTIONS-2 | +| `packages/flutter/oto_console/lib/src/oto_executions_surface.dart` | EXEC_ACTIONS-2 | +| `packages/flutter/oto_console/test/oto_console_test.dart` | EXEC_ACTIONS-2 | + +## 최종 검증 + +```bash +cd apps/client && flutter test +cd packages/flutter/oto_console && flutter test +git diff --check +``` + +예상 결과: 모든 명령이 exit 0이다. Flutter/Go test cache 여부는 해당 없음이며, widget/unit test는 fresh process로 실행된다. + +모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/agent-task/m-control-plane-operator-actions/03+01_runner_actions/CODE_REVIEW-local-G06.md b/agent-task/m-control-plane-operator-actions/03+01_runner_actions/CODE_REVIEW-local-G06.md new file mode 100644 index 0000000..361faf2 --- /dev/null +++ b/agent-task/m-control-plane-operator-actions/03+01_runner_actions/CODE_REVIEW-local-G06.md @@ -0,0 +1,154 @@ + + +# Code Review Reference - RUNNER_ACTIONS + +> **[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 selected SDD decision or selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with linked evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Environment/secret/service blockers, generic scope changes, repeated failures, and evidence gaps that a follow-up agent can close 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 only SDD/Milestone lock decisions 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-18 +task=m-control-plane-operator-actions/03+01_runner_actions, plan=0, tag=RUNNER_ACTIONS + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/control-plane-product-surface/milestones/control-plane-operator-actions.md` +- Task ids: + - `runner-actions`: 기존 runner self-update/status action 표면을 연결하고 실패/대기/완료 상태를 표시한다. +- Completion mode: check-on-pass + +## Spec Targets + +- SDD: `agent-roadmap/sdd/control-plane-product-surface/control-plane-operator-actions/SDD.md` +- Acceptance scenarios: + - `S05`: task=`runner-actions`; evidence=`cd apps/client && flutter test`, `cd packages/flutter/oto_console && flutter test`, self-update accepted/deferred/error tests, `TestHandleRunnerSelfUpdate` reference +- Completion mode: spec-check-on-pass + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. +리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다. + +1. 판정을 append한다. +2. `CODE_REVIEW-local-G06.md` → `code_review_local_G06_N.log`, `PLAN-local-G06.md` → `plan_local_G06_M.log`로 아카이브한다. +3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-control-plane-operator-actions/03+01_runner_actions/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. +4. PASS이고 task group이 `m-control-plane-operator-actions`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다. +5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [RUNNER_ACTIONS-1] Extend Write Adapter For Runner Self Update | [ ] | +| [RUNNER_ACTIONS-2] Add Runner Action Surface State | [ ] | +| [RUNNER_ACTIONS-3] Wire Runner Self Update Through OtoClientApp Refresh | [ ] | + +## 구현 체크리스트 + +- [ ] `01_job_create` predecessor `complete.log`를 확인한 뒤 해당 write adapter/result 패턴을 확장하고 `cd apps/client && flutter test --plain-name "OtoHttpCoreWriteClient maps runner self-update responses"`가 통과하게 한다. +- [ ] `packages/flutter/oto_console`에 runner self-update/status action surface를 추가하고 `cd packages/flutter/oto_console && flutter test --plain-name "OtoRunnersSurface renders runner action states"`가 통과하게 한다. +- [ ] `OtoClientApp`에서 runner self-update action success/deferred 이후 runner status refresh를 연결하고 `cd apps/client && flutter test --plain-name "OtoClientApp wires runner self-update and refresh"`가 통과하게 한다. +- [ ] `cd apps/client && flutter test`와 `cd packages/flutter/oto_console && flutter test`를 실행한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. +> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다. + +- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다. +- [ ] active `CODE_REVIEW-*-G??.md`를 `code_review_local_G06_N.log`로 아카이브한다. +- [ ] active `PLAN-*-G??.md`를 `plan_local_G06_M.log`로 아카이브한다. +- [ ] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다. +- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. +- [ ] PASS이면 active task 디렉터리 `agent-task/m-control-plane-operator-actions/03+01_runner_actions/`를 `agent-task/archive/YYYY/MM/m-control-plane-operator-actions/03+01_runner_actions/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. +- [ ] PASS이고 task group이 `m-control-plane-operator-actions`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다. +- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-control-plane-operator-actions/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. +- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-local-G06.md`와 `CODE_REVIEW-local-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가 연결된 SDD/Milestone 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다. + +## 계획 대비 변경 사항 + +_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._ + +## 주요 설계 결정 + +_구현 에이전트가 주요 설계 결정 사항을 기록한다._ + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 SDD 결정 또는 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 연결 대상: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- `01_job_create`의 completed write result/client 패턴을 재사용했는지 확인한다. +- self-update body가 `runner_id`, `version`, `download_url`만 사용하고 HTTPS URL 원문 secret을 tracked fixture에 남기지 않았는지 확인한다. +- accepted/deferred/error 상태와 runner status refresh가 구분되어 표시되는지 확인한다. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +필수 규칙: +- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다. +- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다. +- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다. +- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다. +- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다. + +### RUNNER_ACTIONS-1 중간 검증 +``` +$ cd apps/client && flutter test --plain-name "OtoHttpCoreWriteClient maps runner self-update responses" +(output) +``` + +### RUNNER_ACTIONS-2 중간 검증 +``` +$ cd packages/flutter/oto_console && flutter test --plain-name "OtoRunnersSurface renders runner action states" +(output) +``` + +### RUNNER_ACTIONS-3 중간 검증 +``` +$ cd apps/client && flutter test --plain-name "OtoClientApp wires runner self-update and refresh" +(output) +``` + +### 최종 검증 +``` +$ cd apps/client && flutter test +(output) + +$ cd packages/flutter/oto_console && flutter test +(output) + +$ git diff --check +(output) +``` + +--- + +> **[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. diff --git a/agent-task/m-control-plane-operator-actions/03+01_runner_actions/PLAN-local-G06.md b/agent-task/m-control-plane-operator-actions/03+01_runner_actions/PLAN-local-G06.md new file mode 100644 index 0000000..a5e7f76 --- /dev/null +++ b/agent-task/m-control-plane-operator-actions/03+01_runner_actions/PLAN-local-G06.md @@ -0,0 +1,287 @@ + + +# Plan - RUNNER_ACTIONS + +## 이 파일을 읽는 구현 에이전트에게 + +구현의 마지막 단계는 반드시 active `CODE_REVIEW-local-G06.md`에서 구현 에이전트 소유 섹션을 실제 변경 내용, 설계 결정, 검증 출력으로 채우는 것이다. 검증 명령을 실행하고 실제 stdout/stderr를 붙인 뒤 active 파일을 그대로 두고 리뷰 준비를 보고한다. 최종 판정, log rename, `complete.log` 작성, archive 이동은 code-review-skill 전용이다. + +선택된 SDD 결정 또는 선택된 Milestone `구현 잠금 > 결정 필요` 항목 때문에 구현이 막히면 active review stub의 `사용자 리뷰 요청` 섹션에 정확한 연결 대상, 증거, 실행한 명령, 재개 조건을 기록하고 멈춘다. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 환경/secret/service 차단, 일반 범위 조정, 검증 증거 공백처럼 후속 agent가 닫을 수 있는 문제는 사용자 리뷰 요청이 아니라 `검증 결과` 또는 `계획 대비 변경 사항`에 기록한다. + +## 배경 + +`runner-actions`는 runner status read surface 위에 self-update write action의 accepted/deferred/error 상태를 표시하는 작업이다. Core에는 `GET /api/v1/runners/{id}/status`와 `POST /api/v1/runners/{id}/self-update`가 이미 존재한다. 이 작업도 `01_job_create`의 write action state pattern을 사용하되, execution action과는 독립적으로 진행할 수 있다. + +## 사용자 리뷰 요청 흐름 + +구현 중 사용자 리뷰가 필요한 경우는 선택된 SDD 결정 또는 선택된 Milestone lock 결정이 실구현을 차단할 때뿐이다. 구현 에이전트는 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`에서 복사된 active review stub의 `사용자 리뷰 요청` 섹션만 채우고 멈춘다. 직접 사용자 프롬프트는 금지되며, code-review가 요청 타당성 검증과 실제 `USER_REVIEW.md` 작성을 소유한다. + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/control-plane-product-surface/milestones/control-plane-operator-actions.md` +- Task ids: + - `runner-actions`: 기존 runner self-update/status action 표면을 연결하고 실패/대기/완료 상태를 표시한다. +- Completion mode: check-on-pass + +## Spec Targets + +- SDD: `agent-roadmap/sdd/control-plane-product-surface/control-plane-operator-actions/SDD.md` +- Acceptance scenarios: + - `S05`: task=`runner-actions`; evidence=`cd apps/client && flutter test`, `cd packages/flutter/oto_console && flutter test`, self-update accepted/deferred/error tests, `TestHandleRunnerSelfUpdate` reference +- Completion mode: spec-check-on-pass + +## 분석 결과 + +### 읽은 파일 + +- `agent-ops/skills/common/plan/SKILL.md` +- `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` +- `agent-test/local/rules.md` +- `agent-roadmap/phase/control-plane-product-surface/milestones/control-plane-operator-actions.md` +- `agent-roadmap/sdd/control-plane-product-surface/control-plane-operator-actions/SDD.md` +- `apps/client/pubspec.yaml` +- `apps/client/lib/src/app/core_connection_client.dart` +- `apps/client/lib/src/app/oto_client_app.dart` +- `apps/client/test/widget_test.dart` +- `packages/flutter/oto_console/pubspec.yaml` +- `packages/flutter/oto_console/lib/src/oto_console_contract.dart` +- `packages/flutter/oto_console/lib/src/oto_console_shell.dart` +- `packages/flutter/oto_console/lib/src/oto_jobs_surface.dart` +- `packages/flutter/oto_console/lib/src/oto_executions_surface.dart` +- `packages/flutter/oto_console/lib/src/oto_runners_surface.dart` +- `packages/flutter/oto_console/lib/src/oto_artifacts_surface.dart` +- `packages/flutter/oto_console/lib/oto_console.dart` +- `packages/flutter/oto_console/test/oto_console_test.dart` +- `services/core/internal/httpserver/routes.go` +- `services/core/internal/httpserver/runner_cicd_handlers.go` +- `services/core/internal/httpserver/dto.go` +- `proto/oto/runner.proto` + +### 테스트 환경 규칙 + +- 선택한 test_env: `local`. +- `agent-test/local/rules.md`가 존재하며 전체를 읽었다. +- 매칭 profile: `apps/client`, `packages/flutter/oto_console`, `services/core`에 대응하는 `agent-test/local/*project-smoke.md`는 없었다. Monorepo Targets가 필요한 Flutter test 명령을 제공하므로 `create-test`는 수행하지 않는다. +- 적용 명령: `cd apps/client && flutter test`, `cd packages/flutter/oto_console && flutter test`. +- Core route/proto는 변경하지 않으므로 `cd services/core && go test ./...`는 필수 명령이 아니다. +- `<확인 필요>` 값은 없었다. + +### 테스트 커버리지 공백 + +- 기존 runner tests는 loading/empty/error/data read rendering만 검증한다. +- self-update write adapter의 HTTPS validation error, accepted, deferred, `error_message` mapping tests가 없다. +- runner surface에는 target version/download URL 입력, submitting/deferred/error/success 표시, callback tests가 없다. +- app-level self-update success/deferred 이후 runner status refresh tests가 없다. + +### 심볼 참조 + +- renamed/removed symbol: none. +- 이 plan은 `01_job_create`가 추가한 write result/client/action state symbol을 확장한다. 구현 시작 전 completed symbol 이름을 확인하고 plan과 다르면 `계획 대비 변경 사항`에 기록한다. + +### 분할 판단 + +- split decision policy를 plan file 선택 전에 평가했다. +- shared task group: `agent-task/m-control-plane-operator-actions/`. +- sibling plans: + - `01_job_create`: 선행 foundation. active dir은 존재하지만 plan 작성 시점에 `complete.log`는 없다. + - `02+01_execution_actions`: predecessor index `01`. + - `03+01_runner_actions`: predecessor index `01`. 구현은 `agent-task/m-control-plane-operator-actions/01_job_create/complete.log` 또는 matching archive `complete.log`가 생긴 뒤 시작한다. +- predecessor status for this plan: missing at plan creation. runtime dependency는 디렉터리명 `03+01_runner_actions`가 source of truth다. + +### 범위 결정 근거 + +- `services/core/internal/httpserver/**`, `proto/oto/runner.proto`는 변경하지 않는다. self-update endpoint와 status read endpoint가 이미 존재한다. +- 이 plan은 runner self-update/status action surface만 다룬다. runner enrollment, bootstrap, auth/audit, private release host/secret 관리는 제외한다. +- execution cancel/report/log/artifact action은 sibling `02+01_execution_actions` 범위다. +- 새 package는 추가하지 않는다. + +### 빌드 등급 + +- 결정: `local-G06`. +- 근거: bounded Flutter adapter/UI/test 작업이며 accepted/deferred/error 상태 구분이 핵심이다. `job-create` foundation을 재사용하므로 execution actions보다 scope가 작다. + +## 구현 체크리스트 + +- [ ] `01_job_create` predecessor `complete.log`를 확인한 뒤 해당 write adapter/result 패턴을 확장하고 `cd apps/client && flutter test --plain-name "OtoHttpCoreWriteClient maps runner self-update responses"`가 통과하게 한다. +- [ ] `packages/flutter/oto_console`에 runner self-update/status action surface를 추가하고 `cd packages/flutter/oto_console && flutter test --plain-name "OtoRunnersSurface renders runner action states"`가 통과하게 한다. +- [ ] `OtoClientApp`에서 runner self-update action success/deferred 이후 runner status refresh를 연결하고 `cd apps/client && flutter test --plain-name "OtoClientApp wires runner self-update and refresh"`가 통과하게 한다. +- [ ] `cd apps/client && flutter test`와 `cd packages/flutter/oto_console && flutter test`를 실행한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +### [RUNNER_ACTIONS-1] Extend Write Adapter For Runner Self Update + +문제: `apps/client/lib/src/app/core_connection_client.dart:147-177`에는 runner status read method만 있고 self-update write method가 없다. Core `handleRunnerSelfUpdate`는 `success`, `accepted`, `deferred`, `message`, `error_message`, `target_version`을 반환하므로 deferred를 success와 구분해야 한다. + +해결 방법: `01_job_create`의 `OtoCoreWriteClient`에 `selfUpdateRunner` method와 `OtoRunnerSelfUpdateDraft`를 추가한다. body는 `runner_id`, `version`, `download_url`만 사용한다. HTTP 200 + `deferred=true`는 `OtoCoreWriteState.deferred`, `accepted=true`는 succeeded, `success=false` 또는 `error_message`는 failed로 변환한다. + +Before: + +```dart +// apps/client/lib/src/app/core_connection_client.dart:153-156 +Future> fetchRunnerStatus( + OtoConsoleConfig config, + String runnerID, +); +``` + +After: + +```dart +abstract class OtoCoreWriteClient { + Future> selfUpdateRunner( + OtoConsoleConfig config, + OtoRunnerSelfUpdateDraft draft, + ); +} +``` + +수정 파일 및 체크리스트: + +- [ ] `apps/client/lib/src/app/core_connection_client.dart`: self-update draft/result model과 write method 추가. +- [ ] `apps/client/lib/src/app/core_connection_client.dart`: `deferred=true`를 별도 state로 변환. +- [ ] `apps/client/test/widget_test.dart`: accepted/deferred/error/invalid HTTPS mapping tests 추가. + +테스트 작성: + +- 작성: `apps/client/test/widget_test.dart` +- 테스트명: `OtoHttpCoreWriteClient maps runner self-update responses` +- assertion goal: `/api/v1/runners/runner-1/self-update` path/body, accepted/deferred/error state, target version/message mapping. +- fixture: `MockClient`, version `v1.2.3`, HTTPS download URL placeholder `https://releases.example.test/oto-agent`. + +중간 검증: + +```bash +cd apps/client && flutter test --plain-name "OtoHttpCoreWriteClient maps runner self-update responses" +``` + +예상 결과: self-update adapter tests가 통과한다. + +### [RUNNER_ACTIONS-2] Add Runner Action Surface State + +문제: `packages/flutter/oto_console/lib/src/oto_runners_surface.dart:4-12`는 snapshot만 받으며, `160-235`의 runner row는 status/current job 정보만 표시한다. self-update target version/download URL 입력과 accepted/deferred/error feedback이 없다. + +해결 방법: console contract에 `OtoRunnerSelfUpdateDraft`와 runner action state를 추가한다. `OtoRunnersSurface`는 optional action state map과 `onSelfUpdateRunner` callback을 받아 runner row에 compact self-update action을 렌더링한다. download URL이 HTTPS가 아니면 submit을 disable하거나 inline validation error를 표시하고, submitting/deferred/succeeded/failed 상태를 분리한다. + +Before: + +```dart +// packages/flutter/oto_console/lib/src/oto_runners_surface.dart:4-12 +class OtoRunnersSurface extends StatelessWidget { + final OtoSurfaceSnapshot> snapshot; + final OtoConsoleThemeAdapter? themeAdapter; + + const OtoRunnersSurface({ + super.key, + required this.snapshot, + this.themeAdapter, + }); +``` + +After: + +```dart +class OtoRunnersSurface extends StatelessWidget { + final Map actionStates; + final ValueChanged? onSelfUpdateRunner; + ... +} +``` + +수정 파일 및 체크리스트: + +- [ ] `packages/flutter/oto_console/lib/src/oto_console_contract.dart`: runner self-update draft/action state 추가. +- [ ] `packages/flutter/oto_console/lib/src/oto_runners_surface.dart`: row-level self-update form과 feedback 추가. +- [ ] `packages/flutter/oto_console/test/oto_console_test.dart`: validation/submitting/deferred/success/error callback tests 추가. + +테스트 작성: + +- 작성: `packages/flutter/oto_console/test/oto_console_test.dart` +- 테스트명: `OtoRunnersSurface renders runner action states` +- assertion goal: HTTPS URL validation, callback payload, submitting disabled, deferred message, error message. +- fixture: `OtoRunnerViewModel(runnerID: 'runner-1', status: 'idle', ...)`. + +중간 검증: + +```bash +cd packages/flutter/oto_console && flutter test --plain-name "OtoRunnersSurface renders runner action states" +``` + +예상 결과: runner action surface tests가 통과한다. + +### [RUNNER_ACTIONS-3] Wire Runner Self Update Through OtoClientApp Refresh + +문제: `apps/client/lib/src/app/oto_client_app.dart:528-531`의 `OtoRunnersSurface` 연결은 snapshot/theme만 전달한다. self-update success/deferred 이후 runner status를 다시 조회하는 경로가 없다. + +해결 방법: app state에 runner action state map과 `_submitRunnerSelfUpdate`를 추가한다. result가 succeeded 또는 deferred면 `_refreshCoreStatus()`를 호출해 `fetchRunner`와 `fetchRunnerStatus`를 다시 실행한다. failed result는 runner snapshot을 임의로 바꾸지 않고 action state만 failed로 표시한다. + +Before: + +```dart +// apps/client/lib/src/app/oto_client_app.dart:528-531 +runners: OtoRunnersSurface( + snapshot: _runnersSnapshot, + themeAdapter: themeAdapter, +), +``` + +After: + +```dart +runners: OtoRunnersSurface( + snapshot: _runnersSnapshot, + actionStates: _runnerActionStates, + onSelfUpdateRunner: _submitRunnerSelfUpdate, + themeAdapter: themeAdapter, +), +``` + +수정 파일 및 체크리스트: + +- [ ] `apps/client/lib/src/app/oto_client_app.dart`: runner action state map and submit method 추가. +- [ ] `apps/client/lib/src/app/oto_client_app.dart`: `OtoRunnersSurface` callbacks 연결. +- [ ] `apps/client/test/widget_test.dart`: fake write client self-update와 refresh assertions 추가. + +테스트 작성: + +- 작성: `apps/client/test/widget_test.dart` +- 테스트명: `OtoClientApp wires runner self-update and refresh` +- assertion goal: self-update callback calls write client, accepted/deferred triggers `fetchRunnerStatus`, failed state displays error and does not mutate runner snapshot. +- fixture: `_FakeCoreWriteClient`, `_FakeCoreReadClient`. + +중간 검증: + +```bash +cd apps/client && flutter test --plain-name "OtoClientApp wires runner self-update and refresh" +``` + +예상 결과: app runner action wiring test가 통과한다. + +## 의존 관계 및 구현 순서 + +1. 구현 시작 전에 predecessor `01_job_create`의 `complete.log`를 확인한다. 디렉터리명 `03+01_runner_actions`의 `+01`이 유일한 runtime dependency다. +2. `RUNNER_ACTIONS-1`에서 completed write adapter pattern을 확장한다. +3. `RUNNER_ACTIONS-2`에서 console runner action state를 추가한다. +4. `RUNNER_ACTIONS-3`에서 app wiring과 refresh behavior를 연결한다. + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `apps/client/lib/src/app/core_connection_client.dart` | RUNNER_ACTIONS-1 | +| `apps/client/lib/src/app/oto_client_app.dart` | RUNNER_ACTIONS-3 | +| `apps/client/test/widget_test.dart` | RUNNER_ACTIONS-1, RUNNER_ACTIONS-3 | +| `packages/flutter/oto_console/lib/src/oto_console_contract.dart` | RUNNER_ACTIONS-2 | +| `packages/flutter/oto_console/lib/src/oto_runners_surface.dart` | RUNNER_ACTIONS-2 | +| `packages/flutter/oto_console/test/oto_console_test.dart` | RUNNER_ACTIONS-2 | + +## 최종 검증 + +```bash +cd apps/client && flutter test +cd packages/flutter/oto_console && flutter test +git diff --check +``` + +예상 결과: 모든 명령이 exit 0이다. Flutter/Go test cache 여부는 해당 없음이며, widget/unit test는 fresh process로 실행된다. + +모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.