From 24983ad72a899046d8879566e0968bd4effb862b Mon Sep 17 00:00:00 2001 From: toki Date: Sun, 31 May 2026 09:08:03 +0900 Subject: [PATCH] feat: operator runtime refactor - API lifecycle capabilities, client bootstrap, and socket handler updates --- agent-roadmap/current.md | 2 +- agent-roadmap/phase/operator-surface/PHASE.md | 2 +- .../milestones/operator-runtime-refactor.md | 10 +- .../code_review_cloud_G07_0.log | 150 ++++++++ .../01_worker_execution/complete.log | 40 +++ .../01_worker_execution/plan_cloud_G07_0.log | 255 ++++++++++++++ .../CODE_REVIEW-cloud-G07.md | 140 ++++++++ .../PLAN-cloud-G07.md | 327 ++++++++++++++++++ .../CODE_REVIEW-cloud-G07.md | 138 ++++++++ .../03_client_bootstrap/PLAN-cloud-G07.md | 290 ++++++++++++++++ services/worker/internal/socket/handlers.go | 33 +- .../worker/internal/socket/server_test.go | 78 +++++ 12 files changed, 1450 insertions(+), 15 deletions(-) create mode 100644 agent-task/archive/2026/05/m-operator-runtime-refactor/01_worker_execution/code_review_cloud_G07_0.log create mode 100644 agent-task/archive/2026/05/m-operator-runtime-refactor/01_worker_execution/complete.log create mode 100644 agent-task/archive/2026/05/m-operator-runtime-refactor/01_worker_execution/plan_cloud_G07_0.log create mode 100644 agent-task/m-operator-runtime-refactor/02+01_api_lifecycle_capabilities/CODE_REVIEW-cloud-G07.md create mode 100644 agent-task/m-operator-runtime-refactor/02+01_api_lifecycle_capabilities/PLAN-cloud-G07.md create mode 100644 agent-task/m-operator-runtime-refactor/03_client_bootstrap/CODE_REVIEW-cloud-G07.md create mode 100644 agent-task/m-operator-runtime-refactor/03_client_bootstrap/PLAN-cloud-G07.md diff --git a/agent-roadmap/current.md b/agent-roadmap/current.md index 5daead1..e5ccd9b 100644 --- a/agent-roadmap/current.md +++ b/agent-roadmap/current.md @@ -7,7 +7,7 @@ ## 활성 Milestone -- [계획] Operator Runtime Reliability Refactor +- [진행중] Operator Runtime Reliability Refactor - Phase: `agent-roadmap/phase/operator-surface/PHASE.md` - 경로: `agent-roadmap/phase/operator-surface/milestones/operator-runtime-refactor.md` diff --git a/agent-roadmap/phase/operator-surface/PHASE.md b/agent-roadmap/phase/operator-surface/PHASE.md index 26ac464..df03dcb 100644 --- a/agent-roadmap/phase/operator-surface/PHASE.md +++ b/agent-roadmap/phase/operator-surface/PHASE.md @@ -16,7 +16,7 @@ Flutter client를 ALT의 공식 operator surface로 삼아 market data 상태와 - [완료] API-Centered Proto-Socket Rail - 경로: `agent-roadmap/archive/phase/operator-surface/milestones/api-centered-proto-socket-rail.md` - 요약: API 서버를 control plane으로 두고 client-api, api-worker 내부 통신을 proto-socket과 ALT protobuf contracts로 통일한다. -- [계획] Operator Runtime Reliability Refactor +- [진행중] Operator Runtime Reliability Refactor - 경로: `agent-roadmap/phase/operator-surface/milestones/operator-runtime-refactor.md` - 요약: Flutter operator console/headless validation 전에 API-worker lifecycle, worker execution wiring, capability reporting, client bootstrap, 원격 테스트 룰을 정리한다. - [계획] Flutter Operator Console UX Plan diff --git a/agent-roadmap/phase/operator-surface/milestones/operator-runtime-refactor.md b/agent-roadmap/phase/operator-surface/milestones/operator-runtime-refactor.md index 157036f..e02db1f 100644 --- a/agent-roadmap/phase/operator-surface/milestones/operator-runtime-refactor.md +++ b/agent-roadmap/phase/operator-surface/milestones/operator-runtime-refactor.md @@ -11,7 +11,7 @@ Flutter operator console 작업을 더 진행하기 전에 API-worker runtime ra ## 상태 -[계획] +[진행중] ## 구현 잠금 @@ -24,7 +24,8 @@ Flutter operator console 작업을 더 진행하기 전에 API-worker runtime ra - worker의 backtest start/execution wiring 또는 명시적 unavailable 상태 정리 - hello capability와 handler registration test를 실제 surface와 일치 - `../nexo/agent-test/local/rules.md`의 원격 실행 기준을 참고해 ALT local test profile을 원격 테스트 host 기준으로 정리 -- Flutter app bootstrap에서 operator shell과 push/Firebase/Mattermost 초기화를 분리 +- Flutter app +- bootstrap에서 operator shell과 push/Firebase/Mattermost 초기화를 분리 - remote verification evidence, generated drift check, skipped integration 조건을 plan/review 루프에서 재현 가능하게 정리 ## 기능 @@ -38,13 +39,13 @@ Operator console과 headless validation이 의존할 API-worker-client rail을 s - [x] [test-rules] `agent-test/local` 규칙을 `../nexo/agent-test/local/rules.md`의 원격 실행 방식에 맞춰 `ssh toki@toki-labs.com` 기준으로 정리하고, ALT smoke profile이 local shell 실행을 암시하지 않게 한다. - [ ] [capabilities] API/worker `HelloResponse.capabilities`와 handler coverage test가 market/backtest/worker availability를 실제 등록 surface와 일치하게 검증한다. - [ ] [client-bootstrap] Flutter operator shell이 push/Firebase/Mattermost 초기화 없이도 시작 가능한 경계로 분리되고, push-specific integration은 후속 push Milestone 범위로 남는다. -- [ ] [verification-handoff] 원격 ALT checkout에서 실행할 검증 명령, `bin/contracts-check`, `bin/worker-storage-check`, 통합 테스트 skip 조건과 실제 stdout/stderr 기록 기준을 plan/review 산출물에 남길 수 있게 정리한다. +- [x] [verification-handoff] 원격 ALT checkout에서 실행할 검증 명령, `bin/contracts-check`, `bin/worker-storage-check`, 통합 테스트 skip 조건과 실제 stdout/stderr 기록 기준을 plan/review 산출물에 남길 수 있게 정리한다. ## 완료 리뷰 - 상태: 없음 - 요청일: 없음 -- 완료 근거: `test-rules`는 Milestone 생성 시점에 반영했다. runtime/client refactor 기능 Task는 아직 충족되지 않았다. +- 완료 근거: `test-rules`는 Milestone 생성 시점에 반영했고, `verification-handoff`는 2026-05-31에 `agent-test/local` handoff 문구와 `m-operator-runtime-refactor` plan/review 산출물로 반영했다. runtime/client refactor 기능 Task는 아직 충족되지 않았다. - 리뷰 필요: - [ ] 사용자가 완료 결과를 확인했다 - [ ] archive 이동을 승인했다 @@ -62,6 +63,7 @@ Operator console과 headless validation이 의존할 API-worker-client rail을 s - 관련 경로: `services/api/`, `services/worker/`, `apps/client/`, `agent-test/local/`, `bin/` - 표준선(선택): API는 thin control plane으로 유지하고 worker execution/storage는 worker에 둔다. 로컬 테스트 profile의 명령은 로컬 shell이 아니라 원격 테스트 host의 ALT checkout에서 실행한다. Client operator shell은 push integration 없이도 기본 실행 가능해야 한다. - 초기 반영: 2026-05-31에 `../nexo/agent-test/local/rules.md`를 참고해 `agent-test/local/rules.md`와 smoke profile의 host/blocker 문구를 원격 테스트 host 기준으로 갱신했다. +- 진행 반영: 2026-05-31에 `agent-test/local` handoff 문구를 보강하고 `agent-task/m-operator-runtime-refactor/` 아래 worker/API/client split plan을 작성했다. - 선행 작업: API-Centered Proto-Socket Rail - 후속 작업: Flutter Operator Console UX Plan, Operator Headless Workflow Validation, Flutter Operator Console MVP - 확인 필요: 없음 diff --git a/agent-task/archive/2026/05/m-operator-runtime-refactor/01_worker_execution/code_review_cloud_G07_0.log b/agent-task/archive/2026/05/m-operator-runtime-refactor/01_worker_execution/code_review_cloud_G07_0.log new file mode 100644 index 0000000..5393664 --- /dev/null +++ b/agent-task/archive/2026/05/m-operator-runtime-refactor/01_worker_execution/code_review_cloud_G07_0.log @@ -0,0 +1,150 @@ + + +# Code Review Reference - WORKER + +> **[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. +> 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-05-31 +task=m-operator-runtime-refactor/01_worker_execution, plan=0, tag=WORKER + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/operator-surface/milestones/operator-runtime-refactor.md` +- Task ids: + - `worker-exec`: `services/worker` main에서 `BacktestStarter`, concrete `KindRunBacktest` handler, engine/store dependency를 연결하거나, 아직 실행 불가인 부분은 capability와 typed error로 명시한다. +- Completion mode: check-on-pass + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. +리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다. + +1. 판정을 append한다. +2. `CODE_REVIEW-cloud-G07.md` -> `code_review_cloud_G07_N.log`, `PLAN-cloud-G07.md` -> `plan_cloud_G07_M.log`로 아카이브한다. +3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-operator-runtime-refactor/01_worker_execution/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. `USER_REVIEW.md`가 사용자 결정으로 완료/PASS 해소되면 code-review가 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log` 작성 후 archive 이동한다. +4. PASS이고 task group이 `m-`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다. +5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [WORKER-1] Worker Capability Truth Source | [x] | +| [WORKER-2] Typed Unavailable And Handler Coverage | [x] | + +## 구현 체크리스트 + +- [x] [WORKER-1] worker hello capabilities를 `Deps` 기반으로 계산해 start surface가 unavailable일 때 execution capability를 광고하지 않게 한다. +- [x] [WORKER-2] worker tests를 보강해 nil starter unavailable response와 hello capability/handler coverage가 실제 registered surface와 일치함을 검증한다. +- [x] 원격 ALT checkout root에서 `go test -count=1 ./services/worker/...`를 실행하고 실제 stdout/stderr를 review stub에 기록한다. +- [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_G07_N.log`로 아카이브한다. +- [x] active `PLAN-*-G??.md`를 `plan_cloud_G07_M.log`로 아카이브한다. +- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하여 plan/review/archive 산출물이 추적 가능한지 확인한다. +- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. +- [x] PASS이면 active task 디렉터리 `agent-task/m-operator-runtime-refactor/01_worker_execution/`를 `agent-task/archive/YYYY/MM/m-operator-runtime-refactor/01_worker_execution/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. +- [x] PASS이고 task group이 `m-operator-runtime-refactor`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다. +- [x] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-operator-runtime-refactor/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. +- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-cloud-G07.md`와 `CODE_REVIEW-cloud-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가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다. + +## 계획 대비 변경 사항 + +- 없음. 계획된 범위와 명세에 맞춰 정확하게 구현을 완료하였습니다. + +## 주요 설계 결정 + +- `socket.Deps` 객체를 `helloHandler`에 클로저 형태로 주입하여 `protoSocket` 패키지의 형식이 지정된 리퀘스트 리스너 시그니처를 깨뜨리지 않으면서, `helloHandler`와 `handleHello`가 dynamic하게 `Deps` 필드들의 non-nil 여부에 따라 worker의 capabilities 목록을 필터링하도록 설계하였습니다. +- `capabilitiesForDeps` 헬퍼 함수를 추가하여, standard capabilities인 `hello`를 기본으로 포함하고, `Instruments`와 `Bars`가 모두 존재할 때 `market-read`, `Analysis`와 `Results`가 모두 존재할 때 `backtest-read`, `Starter`가 존재할 때 `backtest-start` 및 `worker-execution`을 추가하도록 매핑하였습니다. + +## 사용자 리뷰 요청 + +- 상태: 없음 +- 사유 유형: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- Worker hello capability가 `Deps` 실제 availability에서 계산되는지 확인한다. +- `StartBacktestRequest` handler는 계속 등록되지만 nil starter에서는 typed unavailable을 반환하는지 확인한다. +- production engine/store wiring을 억지로 연결하지 않고 unavailable path를 명시했다는 범위 결정을 유지했는지 확인한다. + +## 검증 결과 + +### WORKER-1 중간 검증 +```text +$ go test -count=1 ./services/worker/internal/socket +ok git.toki-labs.com/toki/alt/services/worker/internal/socket 0.060s +``` + +### WORKER-2 중간 검증 +```text +$ go test -count=1 ./services/worker/internal/socket ./services/worker/internal/jobs +ok git.toki-labs.com/toki/alt/services/worker/internal/socket 0.057s +ok git.toki-labs.com/toki/alt/services/worker/internal/jobs 0.002s +``` + +### 최종 검증 +```text +$ go test -count=1 ./services/worker/... +? git.toki-labs.com/toki/alt/services/worker/cmd/alt-worker [no test files] +? git.toki-labs.com/toki/alt/services/worker/cmd/alt-worker-data-check [no test files] +ok git.toki-labs.com/toki/alt/services/worker/cmd/alt-worker-migrate 0.003s +ok git.toki-labs.com/toki/alt/services/worker/internal/backtest 0.002s +ok git.toki-labs.com/toki/alt/services/worker/internal/config 0.004s +ok git.toki-labs.com/toki/alt/services/worker/internal/contracts 0.006s +ok git.toki-labs.com/toki/alt/services/worker/internal/jobs 0.004s +ok git.toki-labs.com/toki/alt/services/worker/internal/marketdata/datacheck0.004s +ok git.toki-labs.com/toki/alt/services/worker/internal/marketdata/importer0.004s +ok git.toki-labs.com/toki/alt/services/worker/internal/providers/kis 0.008s +ok git.toki-labs.com/toki/alt/services/worker/internal/rediskeys 0.004s +ok git.toki-labs.com/toki/alt/services/worker/internal/socket 0.058s +? git.toki-labs.com/toki/alt/services/worker/internal/storage [no test files] +ok git.toki-labs.com/toki/alt/services/worker/internal/storage/postgres 0.096s +? git.toki-labs.com/toki/alt/services/worker/internal/storage/postgres/sqlc [no test files] +``` + +--- + +> **[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로 이동한다. `m-operator-runtime-refactor` 완료 이벤트 메타데이터를 보고하되 roadmap 수정은 런타임 책임으로 남긴다. diff --git a/agent-task/archive/2026/05/m-operator-runtime-refactor/01_worker_execution/complete.log b/agent-task/archive/2026/05/m-operator-runtime-refactor/01_worker_execution/complete.log new file mode 100644 index 0000000..d4c7af2 --- /dev/null +++ b/agent-task/archive/2026/05/m-operator-runtime-refactor/01_worker_execution/complete.log @@ -0,0 +1,40 @@ +# Complete - m-operator-runtime-refactor/01_worker_execution + +## 완료 일시 + +2026-05-31 + +## 요약 + +Worker hello capability truth source를 `socket.Deps` 기반으로 정리하고 capability/test coverage를 보강했다. loop=1, final verdict=PASS. + +## 루프 이력 + +| Plan | Review | Verdict | 메모 | +|------|--------|---------|------| +| `plan_cloud_G07_0.log` | `code_review_cloud_G07_0.log` | PASS | worker capability가 unavailable start를 광고하지 않고 원격 worker test가 통과함 | + +## 구현/정리 내용 + +- `services/worker/internal/socket/handlers.go`에서 hello handler가 `Deps`를 받아 `hello`, `market-read`, `backtest-read`, `backtest-start` capability를 실제 dependency availability 기준으로 계산한다. +- `services/worker/internal/socket/server_test.go`에 capability helper와 worker socket hello regression coverage를 추가했다. + +## 최종 검증 + +- `go test -count=1 ./services/worker/...` - PASS; 원격 ALT checkout에서 재실행했고 worker 전체 패키지가 exit code 0으로 통과했다. +- `git diff --check -- services/worker/internal/socket/handlers.go services/worker/internal/socket/server_test.go` - PASS; whitespace error 없음. + +## Roadmap Completion + +- Milestone: `agent-roadmap/phase/operator-surface/milestones/operator-runtime-refactor.md` +- Completed task ids: + - `worker-exec`: PASS; evidence=`agent-task/archive/2026/05/m-operator-runtime-refactor/01_worker_execution/plan_cloud_G07_0.log`, `agent-task/archive/2026/05/m-operator-runtime-refactor/01_worker_execution/code_review_cloud_G07_0.log`; verification=`go test -count=1 ./services/worker/...` +- Not completed task ids: 없음 + +## 잔여 Nit + +- 없음 + +## 후속 작업 + +- 없음 diff --git a/agent-task/archive/2026/05/m-operator-runtime-refactor/01_worker_execution/plan_cloud_G07_0.log b/agent-task/archive/2026/05/m-operator-runtime-refactor/01_worker_execution/plan_cloud_G07_0.log new file mode 100644 index 0000000..38a4c98 --- /dev/null +++ b/agent-task/archive/2026/05/m-operator-runtime-refactor/01_worker_execution/plan_cloud_G07_0.log @@ -0,0 +1,255 @@ + + +# PLAN - WORKER + +## 이 파일을 읽는 구현 에이전트에게 + +`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채우는 것이 필수다. 구현 후 검증을 실행하고, 실제 변경 내용과 stdout/stderr를 review stub에 남긴 뒤 active 파일을 유지하고 리뷰 준비 상태로 보고한다. 사용자 결정, 사용자 소유 외부 환경, 범위 충돌 없이는 진행할 수 없을 때만 review stub의 `사용자 리뷰 요청`을 채우고 멈춘다. 단순 증거 공백은 후속 에이전트가 재실행으로 메울 수 있으므로 사용자 리뷰 요청이 아니다. + +## 배경 + +현재 worker process는 `KindRunBacktest` placeholder를 등록하고 store-backed read surface만 연결한다. `StartBacktest`는 `Deps.Starter == nil`이면 typed unavailable을 반환하지만, worker hello capability는 항상 `worker-execution`을 광고한다. production `StrategyPort`와 실행 대상 universe adapter가 아직 없으므로, 이번 단계는 실행 가능하다고 속이지 않고 start surface unavailable을 capability와 테스트로 고정한다. + +## 사용자 리뷰 요청 흐름 + +구현 중 차단 사유는 active `CODE_REVIEW-*-G??.md`의 `사용자 리뷰 요청` 섹션에 기록한다. 이 섹션은 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식을 따르며, 실제 `USER_REVIEW.md` 작성 여부는 code-review가 판단한다. + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/operator-surface/milestones/operator-runtime-refactor.md` +- Task ids: + - `worker-exec`: `services/worker` main에서 `BacktestStarter`, concrete `KindRunBacktest` handler, engine/store dependency를 연결하거나, 아직 실행 불가인 부분은 capability와 typed error로 명시한다. +- Completion mode: check-on-pass + +## 분석 결과 + +### 읽은 파일 + +- `agent-ops/rules/project/rules.md` +- `agent-ops/rules/private/rules.md` +- `agent-ops/rules/common/rules-roadmap.md` +- `agent-ops/rules/project/domain/worker/rules.md` +- `agent-ops/rules/project/domain/operations/rules.md` +- `agent-test/local/rules.md` +- `agent-test/local/worker-smoke.md` +- `agent-test/local/operations-smoke.md` +- `agent-roadmap/current.md` +- `agent-roadmap/phase/operator-surface/PHASE.md` +- `agent-roadmap/phase/operator-surface/milestones/operator-runtime-refactor.md` +- `services/worker/cmd/alt-worker/main.go` +- `services/worker/internal/socket/server.go` +- `services/worker/internal/socket/handlers.go` +- `services/worker/internal/socket/backtest.go` +- `services/worker/internal/socket/market.go` +- `services/worker/internal/socket/server_test.go` +- `services/worker/internal/socket/backtest_test.go` +- `services/worker/internal/socket/market_test.go` +- `services/worker/internal/socket/backtest_mapping.go` +- `services/worker/internal/socket/backtest_mapping_test.go` +- `services/worker/internal/jobs/builtin.go` +- `services/worker/internal/jobs/runner.go` +- `services/worker/internal/jobs/backtest_starter.go` +- `services/worker/internal/jobs/backtest_jobs.go` +- `services/worker/internal/jobs/runner_test.go` +- `services/worker/internal/jobs/backtest_starter_test.go` +- `services/worker/internal/jobs/backtest_jobs_test.go` +- `services/worker/internal/backtest/engine.go` +- `services/worker/internal/backtest/engine_test.go` +- `services/worker/internal/backtest/fixture_test.go` +- `services/worker/internal/storage/ports.go` +- `services/worker/internal/storage/postgres/store.go` +- `services/worker/internal/storage/postgres/store_test.go` +- `services/worker/go.mod` +- `packages/domain/backtest/types.go` + +### 테스트 환경 규칙 + +- `test_env=local`. +- `agent-test/local/rules.md`는 존재하고 읽었다. local 검증은 현재 shell에서 직접 실행 금지이며, `ssh toki@toki-labs.com` 접속 후 원격 ALT checkout root에서 profile 명령을 실행한다. +- 매칭 profile은 `agent-test/local/worker-smoke.md`다. worker-only 변경의 기본 명령은 원격 checkout root 기준 `go test ./services/worker/...`이며, fresh run이 필요하므로 plan에서는 `go test -count=1 ./services/worker/...`를 사용한다. +- storage migration/query/generated behavior를 바꾸지 않으면 `bin/worker-storage-check`는 필수 검증이 아니다. 실행이 필요해졌는데 infra나 원격 host가 막히면 pass가 아니라 blocker/skip 근거와 미실행 명령을 기록한다. +- `agent-test/local/operations-smoke.md`는 이번에 작은 verification-handoff 문서 변경을 확인하기 위해 읽었지만, 이 worker plan의 code path 검증 명령은 worker profile을 따른다. + +### 테스트 커버리지 공백 + +- `handleStartBacktest`의 nil starter typed unavailable은 `services/worker/internal/socket/backtest_test.go:112`에서 이미 직접 검증한다. +- worker hello capability가 실제 `Deps`와 일치하는지는 현재 coverage가 없다. `services/worker/internal/socket/server_test.go:59`는 server name/protocol만 확인하고 capability 내용은 검증하지 않는다. +- `KindRunBacktest` concrete handler는 `services/worker/internal/jobs/backtest_jobs_test.go:308`에서 placeholder override를 검증하지만, process main에서는 production engine dependency가 없어서 연결되어 있지 않다. + +### 심볼 참조 + +- renamed/removed symbol: none. +- `handleHello` reference: `services/worker/internal/socket/handlers.go:48`. +- `helloHandler` reference: `services/worker/internal/socket/handlers.go:19`. +- `RegisterBuiltins` reference: `services/worker/cmd/alt-worker/main.go:25`, `services/worker/internal/jobs/runner_test.go:104`, `services/worker/internal/jobs/backtest_jobs_test.go:310`. + +### 분할 판단 + +- split decision policy를 먼저 평가했다. +- shared task group: `m-operator-runtime-refactor`. +- sibling plans: + - `01_worker_execution`: worker execution availability and worker capability truth source. no predecessors. + - `02+01_api_lifecycle_capabilities`: API lifecycle/capability work. depends on `01_worker_execution` so API capability names can align with worker truth. + - `03_client_bootstrap`: Flutter bootstrap separation. independent of worker/API implementation. +- 이 plan은 worker domain 안에서 끝나며 API/client call-site rollout을 포함하지 않는다. storage SQL or schema 변경도 하지 않는다. + +### 범위 결정 근거 + +- production `BacktestStarter`/`Engine` wiring is intentionally excluded because `backtest.Engine` needs a `StrategyPort` and `BarSource` (`services/worker/internal/backtest/engine.go:20`, `services/worker/internal/backtest/engine.go:31`), while current `RunSpec` has no instrument/universe field and `storage.Store.GetBars` reads by instrument id (`services/worker/internal/storage/ports.go:22`). +- The acceptable milestone path is explicit unavailable: keep `Deps.Starter == nil`, keep typed `unavailable` response, and ensure hello capabilities no longer advertise execution. +- API lifecycle and client bootstrap are covered by sibling plans. + +### 빌드 등급 + +- build lane: `cloud-G07`; review lane: `cloud-G07`. +- Worker runtime capability reporting affects proto-socket session semantics and milestone completion, while integration evidence may depend on remote-only verification. + +## 구현 체크리스트 + +- [ ] [WORKER-1] worker hello capabilities를 `Deps` 기반으로 계산해 start surface가 unavailable일 때 execution capability를 광고하지 않게 한다. +- [ ] [WORKER-2] worker tests를 보강해 nil starter unavailable response와 hello capability/handler coverage가 실제 registered surface와 일치함을 검증한다. +- [ ] 원격 ALT checkout root에서 `go test -count=1 ./services/worker/...`를 실행하고 실제 stdout/stderr를 review stub에 기록한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +### [WORKER-1] Worker Capability Truth Source + +#### 문제 + +`services/worker/internal/socket/handlers.go:58` always returns `worker-execution`, even when `services/worker/cmd/alt-worker/main.go:37` builds `socket.Deps` without `Starter`. This conflicts with `services/worker/internal/socket/backtest.go:117`, where start requests return typed unavailable when no starter exists. + +#### 해결 방법 + +Make hello registration close over `Deps`, and compute capabilities from actual dependency availability. + +Before: + +```go +// services/worker/internal/socket/handlers.go:18 +handlers := []sessionHandler{ + helloHandler(), +} +``` + +After: + +```go +handlers := []sessionHandler{ + helloHandler(deps), +} +``` + +Before: + +```go +// services/worker/internal/socket/handlers.go:53 +func handleHello(req *altv1.HelloRequest) (*altv1.HelloResponse, error) { +``` + +After: + +```go +func handleHello(deps Deps, req *altv1.HelloRequest) (*altv1.HelloResponse, error) { +``` + +Capabilities should always include `hello`. Add `market-read` only when both `deps.Instruments` and `deps.Bars` are non-nil. Add `backtest-read` only when `deps.Analysis` and `deps.Results` are non-nil. Add `backtest-start` only when `deps.Starter` is non-nil. Do not add `worker-execution` while start is unavailable. + +#### 수정 파일 및 체크리스트 + +- [ ] `services/worker/internal/socket/handlers.go`: change `helloHandler` and `handleHello` to accept `Deps`. +- [ ] `services/worker/internal/socket/handlers.go`: add a small `capabilitiesForDeps(deps Deps) []string` helper. +- [ ] `services/worker/cmd/alt-worker/main.go`: leave `Starter` nil and keep existing typed unavailable path; no fake starter or placeholder execution should be wired. + +#### 테스트 작성 + +Write tests in `services/worker/internal/socket/server_test.go`: + +- `TestWorkerHelloCapabilitiesReflectDeps`: no deps => only `hello`; market deps => `market-read`; backtest read deps => `backtest-read`; starter => `backtest-start`. +- Keep existing socket hello test and assert it does not advertise `backtest-start` when `BacktestDeps{}` is used. + +#### 중간 검증 + +Run from the remote ALT checkout root: + +```bash +go test -count=1 ./services/worker/internal/socket +``` + +Expected: exit code 0, no missing handler/capability assertions. + +### [WORKER-2] Typed Unavailable And Handler Coverage + +#### 문제 + +`services/worker/internal/socket/backtest_test.go:112` covers the direct nil-starter response, but `services/worker/internal/socket/server_test.go:67` only checks handler uniqueness/hello presence. It does not tie the handler registry to capability claims. + +#### 해결 방법 + +Extend registry tests to compare actual request handlers with capability names. Keep `StartBacktestRequest` registered, because registered start requests should return typed unavailable while `Deps.Starter` is nil. The distinction is capability truth: registered handler exists, execution capability does not. + +Before: + +```go +// services/worker/internal/socket/server_test.go:67 +func TestSessionHandlers(t *testing.T) { +``` + +After: + +```go +func TestSessionHandlers(t *testing.T) { + // keep requestType uniqueness checks +} + +func TestWorkerCapabilitiesDoNotClaimUnavailableStart(t *testing.T) { + // validate capability names from Deps{} +} +``` + +#### 수정 파일 및 체크리스트 + +- [ ] `services/worker/internal/socket/server_test.go`: assert capabilities for `Deps{}` omit `backtest-start`. +- [ ] `services/worker/internal/socket/backtest_test.go`: keep `TestHandleStartBacktestUnavailable`; update only if helper names change. +- [ ] `services/worker/internal/socket/market_test.go`: no change expected unless capability helper uses fake stores from this file. + +#### 테스트 작성 + +Tests are required because this is the regression that completes the roadmap task: the worker must not advertise unavailable execution. + +#### 중간 검증 + +Run from the remote ALT checkout root: + +```bash +go test -count=1 ./services/worker/internal/socket ./services/worker/internal/jobs +``` + +Expected: exit code 0. Existing job tests should still show `RegisterRunBacktestHandler` can override the built-in placeholder when a future production adapter is ready. + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `services/worker/internal/socket/handlers.go` | WORKER-1 | +| `services/worker/internal/socket/server_test.go` | WORKER-1, WORKER-2 | +| `services/worker/internal/socket/backtest_test.go` | WORKER-2 | +| `services/worker/cmd/alt-worker/main.go` | WORKER-1 scope check only | + +## 최종 검증 + +Run from the remote ALT checkout root: + +```bash +go test -count=1 ./services/worker/... +``` + +Expected: exit code 0. Go test cache output is not acceptable for this plan because capability assertions are new. + +If implementation changes storage SQL, migrations, or generated sqlc output, also run from the remote ALT checkout root: + +```bash +bin/worker-storage-check +``` + +Expected: exit code 0, or a recorded blocker/skip with actual stdout/stderr if remote infra is unavailable. + +모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/agent-task/m-operator-runtime-refactor/02+01_api_lifecycle_capabilities/CODE_REVIEW-cloud-G07.md b/agent-task/m-operator-runtime-refactor/02+01_api_lifecycle_capabilities/CODE_REVIEW-cloud-G07.md new file mode 100644 index 0000000..a6925b1 --- /dev/null +++ b/agent-task/m-operator-runtime-refactor/02+01_api_lifecycle_capabilities/CODE_REVIEW-cloud-G07.md @@ -0,0 +1,140 @@ + + +# 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. +> 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-05-31 +task=m-operator-runtime-refactor/02+01_api_lifecycle_capabilities, plan=0, tag=API + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/operator-surface/milestones/operator-runtime-refactor.md` +- Task ids: + - `api-worker-lifecycle`: `services/api`의 worker client 전역 상태와 one-shot connect 흐름을 주입 가능한 lifecycle로 정리하고, connect failure, timeout, disconnect behavior를 테스트한다. + - `capabilities`: API/worker `HelloResponse.capabilities`와 handler coverage test가 market/backtest/worker availability를 실제 등록 surface와 일치하게 검증한다. +- Completion mode: check-on-pass + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. +리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다. + +1. 판정을 append한다. +2. `CODE_REVIEW-cloud-G07.md` -> `code_review_cloud_G07_N.log`, `PLAN-cloud-G07.md` -> `plan_cloud_G07_M.log`로 아카이브한다. +3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-operator-runtime-refactor/02+01_api_lifecycle_capabilities/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. +4. PASS이고 task group이 `m-operator-runtime-refactor`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다. +5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [API-1] Inject Worker Lifecycle | [ ] | +| [API-2] Reconnect And Error Mapping | [ ] | +| [API-3] API Capability Alignment | [ ] | + +## 구현 체크리스트 + +- [ ] [API-1] API server construction에서 package global `socket.Worker`를 제거하고 worker client를 `Server`/handler dependency로 주입한다. +- [ ] [API-2] worker connect failure, timeout, disconnect/reconnect behavior를 handler/lifecycle tests로 고정한다. +- [ ] [API-3] API `HelloResponse.capabilities`를 registered handlers와 worker availability 상태에 맞춰 검증한다. +- [ ] 원격 ALT checkout root에서 `go test -count=1 ./services/api/...`를 실행하고 실제 stdout/stderr를 review stub에 기록한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. +> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다. + +- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다. +- [ ] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G07_N.log`로 아카이브한다. +- [ ] active `PLAN-*-G??.md`를 `plan_cloud_G07_M.log`로 아카이브한다. +- [ ] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하여 plan/review/archive 산출물이 추적 가능한지 확인한다. +- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. +- [ ] PASS이면 active task 디렉터리 `agent-task/m-operator-runtime-refactor/02+01_api_lifecycle_capabilities/`를 `agent-task/archive/YYYY/MM/m-operator-runtime-refactor/02+01_api_lifecycle_capabilities/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. +- [ ] PASS이고 task group이 `m-operator-runtime-refactor`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다. +- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-operator-runtime-refactor/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. +- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-cloud-G07.md`와 `CODE_REVIEW-cloud-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가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다. + +## 계획 대비 변경 사항 + +_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._ + +## 주요 설계 결정 + +_구현 에이전트가 주요 설계 결정 사항을 기록한다._ + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- `socket.Worker` package global이 제거되거나 더 이상 runtime state source가 아닌지 확인한다. +- Worker connect failure/timeout/disconnect가 typed response로 매핑되고 reconnect path가 테스트되는지 확인한다. +- API hello capabilities가 registered API handler surface와 worker availability를 반영하는지 확인한다. +- `01_worker_execution`의 completed capability naming과 불일치가 없는지 확인한다. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +필수 규칙: +- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다. +- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다. +- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다. +- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다. + +### API-1 중간 검증 +```text +$ go test -count=1 ./services/api/internal/socket +(output) +``` + +### API-2 중간 검증 +```text +$ go test -count=1 ./services/api/internal/socket ./services/api/internal/workerclient +(output) +``` + +### API-3 중간 검증 +```text +$ go test -count=1 ./services/api/internal/socket +(output) +``` + +### 최종 검증 +```text +$ go test -count=1 ./services/api/... +(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-operator-runtime-refactor/02+01_api_lifecycle_capabilities/PLAN-cloud-G07.md b/agent-task/m-operator-runtime-refactor/02+01_api_lifecycle_capabilities/PLAN-cloud-G07.md new file mode 100644 index 0000000..8f6a4c8 --- /dev/null +++ b/agent-task/m-operator-runtime-refactor/02+01_api_lifecycle_capabilities/PLAN-cloud-G07.md @@ -0,0 +1,327 @@ + + +# PLAN - API + +## 이 파일을 읽는 구현 에이전트에게 + +`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 반드시 채운다. 구현 후 검증 명령의 실제 stdout/stderr를 붙이고, active plan/review 파일을 그대로 둔 채 리뷰 준비 상태로 보고한다. 사용자 결정, 사용자 소유 외부 환경, 범위 충돌 없이는 안전하게 진행할 수 없을 때만 review stub의 `사용자 리뷰 요청`을 채우고 멈춘다. 후속 에이전트가 명령 재실행으로 해소할 수 있는 증거 공백은 사용자 리뷰 요청이 아니다. + +## 배경 + +현재 API server construction이 package global `socket.Worker`를 덮어쓰고, process main은 worker connect error를 goroutine 안에서 버린다. Handler tests도 global swap에 의존해 병렬성/격리가 약하다. 이 계획은 API를 thin control plane으로 유지하면서 worker client lifecycle을 주입 가능하게 만들고, hello capability가 registered handler와 worker availability를 반영하게 한다. + +## 사용자 리뷰 요청 흐름 + +구현 중 차단 사유는 active `CODE_REVIEW-*-G??.md`의 `사용자 리뷰 요청` 섹션에 기록한다. code-review가 정당성을 확인한 뒤에만 `USER_REVIEW.md`를 작성한다. + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/operator-surface/milestones/operator-runtime-refactor.md` +- Task ids: + - `api-worker-lifecycle`: `services/api`의 worker client 전역 상태와 one-shot connect 흐름을 주입 가능한 lifecycle로 정리하고, connect failure, timeout, disconnect behavior를 테스트한다. + - `capabilities`: API/worker `HelloResponse.capabilities`와 handler coverage test가 market/backtest/worker availability를 실제 등록 surface와 일치하게 검증한다. +- Completion mode: check-on-pass + +## 분석 결과 + +### 읽은 파일 + +- `agent-ops/rules/project/rules.md` +- `agent-ops/rules/private/rules.md` +- `agent-ops/rules/common/rules-roadmap.md` +- `agent-ops/rules/project/domain/api/rules.md` +- `agent-ops/rules/project/domain/worker/rules.md` +- `agent-ops/rules/project/domain/operations/rules.md` +- `agent-test/local/rules.md` +- `agent-test/local/api-smoke.md` +- `agent-test/local/worker-smoke.md` +- `agent-test/local/contracts-smoke.md` +- `agent-roadmap/current.md` +- `agent-roadmap/phase/operator-surface/PHASE.md` +- `agent-roadmap/phase/operator-surface/milestones/operator-runtime-refactor.md` +- `services/api/cmd/alt-api/main.go` +- `services/api/internal/config/config.go` +- `services/api/internal/config/config_test.go` +- `services/api/internal/socket/server.go` +- `services/api/internal/socket/handlers.go` +- `services/api/internal/socket/backtest.go` +- `services/api/internal/socket/market.go` +- `services/api/internal/socket/server_test.go` +- `services/api/internal/socket/backtest_test.go` +- `services/api/internal/socket/market_test.go` +- `services/api/internal/workerclient/client.go` +- `services/api/internal/workerclient/client_test.go` +- `services/api/go.mod` +- `services/worker/internal/socket/handlers.go` +- `services/worker/internal/socket/server_test.go` + +### 테스트 환경 규칙 + +- `test_env=local`. +- `agent-test/local/rules.md`는 존재하고 읽었다. 검증 명령은 현재 shell에서 직접 실행하지 않고 remote host에서 ALT checkout root 기준으로 실행한다. +- 매칭 profile은 `agent-test/local/api-smoke.md`다. API-only 변경 기본 명령은 `go test ./services/api/...`; fresh run이 필요하므로 `go test -count=1 ./services/api/...`를 사용한다. +- Parser/contract schema를 바꾸지 않으므로 `agent-test/local/contracts-smoke.md`의 `bin/contracts-check`는 필수 검증이 아니다. Protobuf schema/generated output을 건드리면 이 plan 범위 이탈로 기록하고 contracts check를 추가해야 한다. +- Worker capability names depend on predecessor `01_worker_execution`; this dependent task must not start before that subtask has `complete.log`. + +### 테스트 커버리지 공백 + +- `services/api/internal/workerclient/client_test.go:17` covers unavailable connect, and `services/api/internal/workerclient/client_test.go:209` covers request timeout. +- Disconnect/reconnect behavior is not covered. `sendTyped` returns `ErrUnavailable` when `wsClient` is nil/dead (`services/api/internal/workerclient/client.go:163`) but no API handler tries to reconnect before forwarding. +- `services/api/internal/socket/server_test.go:60` only asserts capabilities are non-empty; it does not verify registered API handlers or worker availability capability. +- Handler tests mutate global `Worker` through `withWorker` (`services/api/internal/socket/backtest_test.go:71`), so dependency injection/lifecycle behavior is not isolated. + +### 심볼 참조 + +- removed/renamed symbol candidate: package global `socket.Worker`. +- References found: + - `services/api/internal/socket/server.go:18` + - `services/api/internal/socket/server.go:21` + - `services/api/cmd/alt-api/main.go:23` + - `services/api/cmd/alt-api/main.go:38` + - `services/api/internal/socket/backtest.go:68`, `84`, `103`, `122`, `146` + - `services/api/internal/socket/market.go:43`, `76` + - tests: `services/api/internal/socket/backtest_test.go:74`, `75`, `76` + +### 분할 판단 + +- split decision policy를 먼저 평가했다. +- shared task group: `m-operator-runtime-refactor`. +- this subtask: `02+01_api_lifecycle_capabilities`. +- predecessor `01`: expected active path `agent-task/m-operator-runtime-refactor/01_worker_execution/complete.log`; currently missing because the predecessor plan was just created. Runtime dependency comes from the directory name, so implementation must wait until predecessor PASS writes `complete.log`. +- API work is split from worker because it removes package global state and changes lifecycle semantics across handlers/tests. Client bootstrap is independent and remains in `03_client_bootstrap`. + +### 범위 결정 근거 + +- Do not change protobuf schema or generated code. `HelloResponse.capabilities` already exists in generated contracts. +- Do not make API run backtest execution or import storage dependencies; API remains a thin forwarder. +- Do not implement Flutter UI changes here. +- Do not make worker availability a hard API startup requirement. API should start and report worker unavailable when connect fails. + +### 빌드 등급 + +- build lane: `cloud-G07`; review lane: `cloud-G07`. +- This changes lifecycle/concurrency boundaries, removes global state, and affects client-visible capability semantics. + +## 구현 체크리스트 + +- [ ] [API-1] API server construction에서 package global `socket.Worker`를 제거하고 worker client를 `Server`/handler dependency로 주입한다. +- [ ] [API-2] worker connect failure, timeout, disconnect/reconnect behavior를 handler/lifecycle tests로 고정한다. +- [ ] [API-3] API `HelloResponse.capabilities`를 registered handlers와 worker availability 상태에 맞춰 검증한다. +- [ ] 원격 ALT checkout root에서 `go test -count=1 ./services/api/...`를 실행하고 실제 stdout/stderr를 review stub에 기록한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 의존 관계 및 구현 순서 + +`02+01_api_lifecycle_capabilities`는 `01_worker_execution`의 `complete.log`가 있어야 구현을 시작한다. worker side capability names and unavailable/start semantics must be known before API hello capability assertions are finalized. + +### [API-1] Inject Worker Lifecycle + +#### 문제 + +`services/api/internal/socket/server.go:18` declares package global `Worker`, and `NewServer` overwrites it at `services/api/internal/socket/server.go:21`. `services/api/cmd/alt-api/main.go:22` starts a goroutine that ignores `socket.Worker.Connect(ctx)` errors. This hides connect failures and makes tests mutate shared state. + +#### 해결 방법 + +Introduce an API `Server` wrapper and dependency injection. + +Before: + +```go +// services/api/internal/socket/server.go:18 +var Worker workerclient.WorkerClient + +func NewServer(cfg config.Config) *protoSocket.WsServer { + Worker = workerclient.New(cfg.WorkerSocketURL) +``` + +After: + +```go +type Server struct { + wsServer *protoSocket.WsServer + worker workerclient.WorkerClient +} + +func NewServer(cfg config.Config) *Server { + return NewServerWithWorker(cfg, workerclient.New(cfg.WorkerSocketURL)) +} +``` + +Register handlers with the injected worker: + +```go +server.wsServer.OnClientConnected = registerSessionHandlers(worker) +``` + +Move close ownership into `Server.Stop`, and remove `socket.Worker.Connect/Close` from `cmd/alt-api/main.go`. API startup may attempt eager connect and log failure, but handlers must still operate when worker is unavailable. + +#### 수정 파일 및 체크리스트 + +- [ ] `services/api/internal/socket/server.go`: add `Server`, `NewServerWithWorker`, `Start`, `Stop`. +- [ ] `services/api/internal/socket/handlers.go`: make `sessionHandlers(worker)` and `registerSessionHandlers(worker)` close over injected worker. +- [ ] `services/api/internal/socket/backtest.go`: pass `workerclient.WorkerClient` into handlers instead of reading global `Worker`. +- [ ] `services/api/internal/socket/market.go`: same injection change. +- [ ] `services/api/cmd/alt-api/main.go`: remove direct global connect/close usage. + +#### 테스트 작성 + +Update tests to use `NewServerWithWorker(cfg, fake)` and remove `withWorker` global swap where possible. Add a test that two servers can be built with different fake workers without cross-talk. + +#### 중간 검증 + +Run from the remote ALT checkout root: + +```bash +go test -count=1 ./services/api/internal/socket +``` + +Expected: exit code 0 and no package global worker dependency. + +### [API-2] Reconnect And Error Mapping + +#### 문제 + +`services/api/internal/workerclient/client.go:56` has idempotent `Connect`, but API handlers never call it after startup. If a worker disconnects, `sendTyped` returns `ErrUnavailable` (`services/api/internal/workerclient/client.go:163`) until an external reconnect happens. There is no test for reconnect behavior. + +#### 해결 방법 + +Before each forwarded worker request, ensure the injected worker is connected with the request timeout context. Since `Connect` is already idempotent, calling it in the request path handles initial connect failure and later disconnects without a second lifecycle manager. + +Before: + +```go +// services/api/internal/socket/backtest.go:71 +ctx, cancel := context.WithTimeout(context.Background(), workerRequestTimeout) +defer cancel() +res, err := Worker.StartBacktest(ctx, req) +``` + +After: + +```go +ctx, cancel := context.WithTimeout(context.Background(), workerRequestTimeout) +defer cancel() +if err := worker.Connect(ctx); err != nil { + return &altv1.StartBacktestResponse{Error: workerErrorInfo(err)}, nil +} +res, err := worker.StartBacktest(ctx, req) +``` + +Apply the same helper path for market and backtest forwarding to keep error mapping consistent. + +#### 수정 파일 및 체크리스트 + +- [ ] `services/api/internal/socket/backtest.go`: add shared worker connect/forward helper or call `worker.Connect(ctx)` consistently. +- [ ] `services/api/internal/socket/market.go`: same behavior. +- [ ] `services/api/internal/workerclient/client.go`: add `IsConnected() bool` only if API hello capability needs it; keep `Connect` idempotent. +- [ ] `services/api/internal/workerclient/client_test.go`: add reconnect after close/dead connection coverage if implementation changes client state methods. + +#### 테스트 작성 + +Add fake worker fields for connect count and connect error. Required tests: + +- connect failure returns typed unavailable without forwarding request. +- context timeout maps to timeout. +- after fake disconnect state, next valid handler calls `Connect` again before forwarding. + +#### 중간 검증 + +Run from the remote ALT checkout root: + +```bash +go test -count=1 ./services/api/internal/socket ./services/api/internal/workerclient +``` + +Expected: exit code 0; timeout/unavailable mapping remains typed response payloads. + +### [API-3] API Capability Alignment + +#### 문제 + +`services/api/internal/socket/handlers.go:66` returns static capabilities with `hello`, `request-response`, and `market-read`. The API now registers backtest handlers (`services/api/internal/socket/backtest_test.go:442`) but does not advertise them, and it cannot represent worker availability. + +#### 해결 방법 + +Build capabilities from registered handler groups and worker connection state. Minimum expected capability set: + +- always: `hello`, `request-response` +- registered API surface: `market-read`, `backtest-read`, `backtest-start` +- worker state: `worker-available` when connected/alive, otherwise `worker-unavailable` + +Keep names aligned with `01_worker_execution`. If predecessor chose different exact names, use those names here and record the change in review. + +Before: + +```go +// services/api/internal/socket/handlers.go:70 +Capabilities: []string{ + "hello", + "request-response", + "market-read", +}, +``` + +After: + +```go +Capabilities: capabilitiesForSession(worker) +``` + +#### 수정 파일 및 체크리스트 + +- [ ] `services/api/internal/socket/handlers.go`: make `helloHandler(worker)` and `handleHello(worker, req)`. +- [ ] `services/api/internal/socket/server_test.go`: assert hello capabilities include registered market/backtest API surface and worker availability state. +- [ ] `services/api/internal/socket/backtest_test.go` and `market_test.go`: keep handler coverage tests aligned with capability helper. + +#### 테스트 작성 + +Tests are required: + +- `TestServerRespondsToHelloRequest` should assert exact capability set for an unavailable fake worker. +- A unit test should assert capability names and handler registration stay in sync. +- If `workerclient.IsConnected()` is added, test both true and false fake states. + +#### 중간 검증 + +Run from the remote ALT checkout root: + +```bash +go test -count=1 ./services/api/internal/socket +``` + +Expected: exit code 0 and deterministic capability assertions. + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `services/api/internal/socket/server.go` | API-1 | +| `services/api/internal/socket/handlers.go` | API-1, API-3 | +| `services/api/internal/socket/backtest.go` | API-1, API-2 | +| `services/api/internal/socket/market.go` | API-1, API-2 | +| `services/api/cmd/alt-api/main.go` | API-1 | +| `services/api/internal/workerclient/client.go` | API-2, API-3 if needed | +| `services/api/internal/socket/server_test.go` | API-1, API-3 | +| `services/api/internal/socket/backtest_test.go` | API-1, API-2, API-3 | +| `services/api/internal/socket/market_test.go` | API-1, API-2, API-3 | +| `services/api/internal/workerclient/client_test.go` | API-2 | + +## 최종 검증 + +Run from the remote ALT checkout root: + +```bash +go test -count=1 ./services/api/... +``` + +Expected: exit code 0. Go test cache output is not acceptable because lifecycle and capability tests are new. + +If implementation changes protobuf schema or generated contract outputs, stop and record scope change; then run from the remote ALT checkout root: + +```bash +bin/contracts-check +``` + +Expected: exit code 0 with actual stdout/stderr, or documented blocker. Schema/generated changes are not expected in this plan. + +모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/agent-task/m-operator-runtime-refactor/03_client_bootstrap/CODE_REVIEW-cloud-G07.md b/agent-task/m-operator-runtime-refactor/03_client_bootstrap/CODE_REVIEW-cloud-G07.md new file mode 100644 index 0000000..4cb4b1f --- /dev/null +++ b/agent-task/m-operator-runtime-refactor/03_client_bootstrap/CODE_REVIEW-cloud-G07.md @@ -0,0 +1,138 @@ + + +# Code Review Reference - CLIENT + +> **[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. +> 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-05-31 +task=m-operator-runtime-refactor/03_client_bootstrap, plan=0, tag=CLIENT + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/operator-surface/milestones/operator-runtime-refactor.md` +- Task ids: + - `client-bootstrap`: Flutter operator shell이 push/Firebase/Mattermost 초기화 없이도 시작 가능한 경계로 분리되고, push-specific integration은 후속 push Milestone 범위로 남는다. +- Completion mode: check-on-pass + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. +리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다. + +1. 판정을 append한다. +2. `CODE_REVIEW-cloud-G07.md` -> `code_review_cloud_G07_N.log`, `PLAN-cloud-G07.md` -> `plan_cloud_G07_M.log`로 아카이브한다. +3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-operator-runtime-refactor/03_client_bootstrap/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. +4. PASS이고 task group이 `m-operator-runtime-refactor`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다. +5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [CLIENT-1] Core Shell Bootstrap | [ ] | +| [CLIENT-2] App Shell Starts Without Push | [ ] | +| [CLIENT-3] Push Opt-In Boundary | [ ] | + +## 구현 체크리스트 + +- [ ] [CLIENT-1] core operator shell bootstrap을 Firebase/Mattermost push initialization과 분리한다. +- [ ] [CLIENT-2] bootstrap tests를 추가해 기본 `runAltClient` path가 push/Firebase 없이 app shell을 시작할 수 있음을 검증한다. +- [ ] [CLIENT-3] push-specific initialization은 explicit opt-in API로 남기고 후속 push Milestone 범위임을 코드 경계로 드러낸다. +- [ ] 원격 ALT checkout root에서 `cd apps/client && flutter test`를 실행하고 실제 stdout/stderr를 review stub에 기록한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. +> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다. + +- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다. +- [ ] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G07_N.log`로 아카이브한다. +- [ ] active `PLAN-*-G??.md`를 `plan_cloud_G07_M.log`로 아카이브한다. +- [ ] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하여 plan/review/archive 산출물이 추적 가능한지 확인한다. +- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. +- [ ] PASS이면 active task 디렉터리 `agent-task/m-operator-runtime-refactor/03_client_bootstrap/`를 `agent-task/archive/YYYY/MM/m-operator-runtime-refactor/03_client_bootstrap/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. +- [ ] PASS이고 task group이 `m-operator-runtime-refactor`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다. +- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-operator-runtime-refactor/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. +- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-cloud-G07.md`와 `CODE_REVIEW-cloud-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가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다. + +## 계획 대비 변경 사항 + +_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._ + +## 주요 설계 결정 + +_구현 에이전트가 주요 설계 결정 사항을 기록한다._ + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- Default `runAltClient`/`bootstrapAltClient` path does not require Firebase or Mattermost push readiness. +- Push initialization is explicit opt-in and remains outside dashboard/widget presentation code. +- New tests avoid platform Firebase initialization by using injected hooks/fakes. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +필수 규칙: +- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다. +- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다. +- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다. +- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다. + +### CLIENT-1 중간 검증 +```text +$ cd apps/client && flutter test test/app/bootstrap_test.dart +(output) +``` + +### CLIENT-2 중간 검증 +```text +$ cd apps/client && flutter test test/widget_test.dart test/app/bootstrap_test.dart +(output) +``` + +### CLIENT-3 중간 검증 +```text +$ cd apps/client && flutter test test/integrations/mattermost_push_host_integration_test.dart +(output) +``` + +### 최종 검증 +```text +$ cd apps/client && flutter test +(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-operator-runtime-refactor/03_client_bootstrap/PLAN-cloud-G07.md b/agent-task/m-operator-runtime-refactor/03_client_bootstrap/PLAN-cloud-G07.md new file mode 100644 index 0000000..3a1a4b8 --- /dev/null +++ b/agent-task/m-operator-runtime-refactor/03_client_bootstrap/PLAN-cloud-G07.md @@ -0,0 +1,290 @@ + + +# PLAN - CLIENT + +## 이 파일을 읽는 구현 에이전트에게 + +`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 구현이 막히면 active review stub의 `사용자 리뷰 요청`에 정확한 결정/근거/실행 명령을 기록하고 멈춘다. 후속 에이전트가 재실행으로 메울 수 있는 검증 증거 공백은 사용자 리뷰 요청이 아니다. + +## 배경 + +Flutter app bootstrap currently initializes Firebase and Mattermost push before rendering the operator shell. The current Milestone requires the operator shell to start without push/Firebase/Mattermost readiness, while push behavior remains a later Milestone. This plan separates core shell bootstrap from optional push integration without changing dashboard UI or generated contracts. + +## 사용자 리뷰 요청 흐름 + +구현 중 사용자 결정이 필요한 경우 active `CODE_REVIEW-*-G??.md`의 `사용자 리뷰 요청` 섹션을 채우고 멈춘다. code-review가 검증한 경우에만 `USER_REVIEW.md`를 작성한다. + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/operator-surface/milestones/operator-runtime-refactor.md` +- Task ids: + - `client-bootstrap`: Flutter operator shell이 push/Firebase/Mattermost 초기화 없이도 시작 가능한 경계로 분리되고, push-specific integration은 후속 push Milestone 범위로 남는다. +- Completion mode: check-on-pass + +## 분석 결과 + +### 읽은 파일 + +- `agent-ops/rules/project/rules.md` +- `agent-ops/rules/private/rules.md` +- `agent-ops/rules/common/rules-roadmap.md` +- `agent-ops/rules/project/domain/client/rules.md` +- `agent-ops/rules/project/domain/operations/rules.md` +- `agent-test/local/rules.md` +- `agent-test/local/client-smoke.md` +- `agent-roadmap/current.md` +- `agent-roadmap/phase/operator-surface/PHASE.md` +- `agent-roadmap/phase/operator-surface/milestones/operator-runtime-refactor.md` +- `apps/client/lib/main.dart` +- `apps/client/lib/src/app/bootstrap.dart` +- `apps/client/lib/src/app/app.dart` +- `apps/client/test/widget_test.dart` +- `apps/client/test/integrations/mattermost_push_host_integration_test.dart` +- `apps/client/integration_test/socket_runtime_smoke_test.dart` +- `apps/client/pubspec.yaml` + +### 테스트 환경 규칙 + +- `test_env=local`. +- `agent-test/local/rules.md`는 존재하고 읽었다. local shell에서 테스트를 직접 실행하지 않고 remote host의 ALT checkout root에서 실행한다. +- 매칭 profile은 `agent-test/local/client-smoke.md`다. client-only 변경 기본 명령은 `cd apps/client && flutter test`. +- Socket runtime/integration smoke는 API socket/platform runtime이 필요하면 실행 가능 여부를 확인하고, 실행할 수 없으면 pass가 아니라 skip/blocker와 미실행 명령을 기록한다. + +### 테스트 커버리지 공백 + +- `apps/client/test/widget_test.dart:18` already proves `AltClientApp` can render without a `mattermostHost`, but it bypasses `runAltClient` and `bootstrapAltClient`. +- `apps/client/lib/src/app/bootstrap.dart:25` has no unit test proving Firebase/push are optional. +- Mattermost host integration tests cover host initialization behavior in isolation (`apps/client/test/integrations/mattermost_push_host_integration_test.dart:91`) but not app bootstrap separation. + +### 심볼 참조 + +- renamed/removed symbol candidate: none required. +- `bootstrapAltClient` references: `apps/client/lib/src/app/bootstrap.dart:25`, `apps/client/lib/src/app/bootstrap.dart:33`. +- `runAltClient` references: `apps/client/lib/main.dart:3`, `apps/client/lib/src/app/bootstrap.dart:31`. +- `MattermostPushHostIntegration` app references: `apps/client/lib/src/app/bootstrap.dart:6`, `apps/client/lib/src/app/app.dart:6`. + +### 분할 판단 + +- split decision policy를 먼저 평가했다. +- shared task group: `m-operator-runtime-refactor`. +- this subtask: `03_client_bootstrap`, no predecessors. +- It is independent because it touches Flutter bootstrap/test boundaries only. It must not wait for worker/API runtime plans and must not implement push notification behavior itself. + +### 범위 결정 근거 + +- Do not change dashboard layout, navigation depth, chart/form wireframe, or production operator screens. +- Do not edit generated Dart protobuf files. +- Do not remove Mattermost/Firebase integration packages; only make them optional at app bootstrap. +- Do not implement push notification behavior. Push-specific integration remains in the later push Milestone. + +### 빌드 등급 + +- build lane: `cloud-G07`; review lane: `cloud-G07`. +- Flutter bootstrap touches platform/Firebase startup and optional external integration boundaries; tests are runnable but runtime smoke may be environment-gated. + +## 구현 체크리스트 + +- [ ] [CLIENT-1] core operator shell bootstrap을 Firebase/Mattermost push initialization과 분리한다. +- [ ] [CLIENT-2] bootstrap tests를 추가해 기본 `runAltClient` path가 push/Firebase 없이 app shell을 시작할 수 있음을 검증한다. +- [ ] [CLIENT-3] push-specific initialization은 explicit opt-in API로 남기고 후속 push Milestone 범위임을 코드 경계로 드러낸다. +- [ ] 원격 ALT checkout root에서 `cd apps/client && flutter test`를 실행하고 실제 stdout/stderr를 review stub에 기록한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +### [CLIENT-1] Core Shell Bootstrap + +#### 문제 + +`apps/client/lib/src/app/bootstrap.dart:25` calls `Firebase.initializeApp()` and `_mattermostHost.initialize()` before `runApp`. This makes optional push readiness a prerequisite for the operator shell, contrary to the client domain rule and current Milestone. + +#### 해결 방법 + +Split bootstrap into core shell setup and optional push setup. Keep `applyFullscreenMode` in the core path. Return or pass a nullable `MattermostPushHostIntegration` only when push is explicitly enabled. + +Before: + +```dart +// apps/client/lib/src/app/bootstrap.dart:25 +Future bootstrapAltClient() async { + await applyFullscreenMode(); + await Firebase.initializeApp(); + await _mattermostHost.initialize(); +} +``` + +After: + +```dart +class AltClientBootstrapOptions { + const AltClientBootstrapOptions({this.enablePush = false}); + final bool enablePush; +} + +Future bootstrapAltClient({ + AltClientBootstrapOptions options = const AltClientBootstrapOptions(), +}) async { + await applyFullscreenMode(); + if (!options.enablePush) return null; + await Firebase.initializeApp(); + await _mattermostHost.initialize(); + return _mattermostHost; +} +``` + +Allow injectable functions or host factory if needed for tests; do not call Firebase in the default test path. + +#### 수정 파일 및 체크리스트 + +- [ ] `apps/client/lib/src/app/bootstrap.dart`: introduce bootstrap options/result and default push-off core bootstrap. +- [ ] `apps/client/lib/main.dart`: keep `main` simple; default `runAltClient()` should render the core shell. +- [ ] `apps/client/lib/src/app/app.dart`: no change expected unless constructor docs or nullable host behavior needs tightening. + +#### 테스트 작성 + +Add `apps/client/test/app/bootstrap_test.dart` or equivalent: + +- default `bootstrapAltClient` does not call injected Firebase/push functions. +- opt-in push path calls injected Firebase and host initialize once. + +#### 중간 검증 + +Run from the remote ALT checkout root: + +```bash +cd apps/client && flutter test test/app/bootstrap_test.dart +``` + +Expected: exit code 0. + +### [CLIENT-2] App Shell Starts Without Push + +#### 문제 + +`apps/client/test/widget_test.dart:19` instantiates `AltClientApp` directly, so it does not protect the `runAltClient` bootstrap path. + +#### 해결 방법 + +Make `runAltClient` accept injectable `runApp`/bootstrap hooks or a testable `buildAltClientApp` helper. Keep tests simple and avoid launching platform Firebase. + +Before: + +```dart +// apps/client/lib/src/app/bootstrap.dart:31 +Future runAltClient() async { + WidgetsFlutterBinding.ensureInitialized(); + await bootstrapAltClient(); + runApp(ProviderScope(child: AltClientApp(mattermostHost: _mattermostHost))); +} +``` + +After: + +```dart +Future runAltClient({ + AltClientBootstrapOptions options = const AltClientBootstrapOptions(), + void Function(Widget app)? runAppOverride, +}) async { + WidgetsFlutterBinding.ensureInitialized(); + final mattermostHost = await bootstrapAltClient(options: options); + (runAppOverride ?? runApp)( + ProviderScope(child: AltClientApp(mattermostHost: mattermostHost)), + ); +} +``` + +If `runAppOverride` creates awkward production API, prefer a private testable helper that returns the root widget after bootstrap. + +#### 수정 파일 및 체크리스트 + +- [ ] `apps/client/lib/src/app/bootstrap.dart`: make default run path pass `mattermostHost: null`. +- [ ] `apps/client/test/widget_test.dart`: keep direct shell tests passing. +- [ ] `apps/client/test/app/bootstrap_test.dart`: assert default run path builds `AltClientApp` without a Mattermost host. + +#### 테스트 작성 + +Required. Use injected callbacks/fakes so no Firebase platform initialization is needed. + +#### 중간 검증 + +Run from the remote ALT checkout root: + +```bash +cd apps/client && flutter test test/widget_test.dart test/app/bootstrap_test.dart +``` + +Expected: exit code 0. + +### [CLIENT-3] Push Opt-In Boundary + +#### 문제 + +`apps/client/lib/src/app/bootstrap.dart:10` creates a package-level `_mattermostHost` and immediately uses it during default bootstrap. This hides the optional boundary and invites later push work to leak into the operator shell. + +#### 해결 방법 + +Keep the host factory private but only instantiate or initialize it when push is explicitly enabled. Existing `MattermostPushHostIntegration` tests remain the behavior owner for push-specific logic. + +Before: + +```dart +// apps/client/lib/src/app/bootstrap.dart:10 +final _mattermostHost = MattermostPushHostIntegration( + pushClient: MattermostPushPluginClient(), +); +``` + +After: + +```dart +MattermostPushHostIntegration createMattermostPushHost() { + return MattermostPushHostIntegration( + pushClient: MattermostPushPluginClient(), + ); +} +``` + +Use the factory only in opt-in push bootstrap. If a singleton is retained, ensure it is not initialized on the default path. + +#### 수정 파일 및 체크리스트 + +- [ ] `apps/client/lib/src/app/bootstrap.dart`: isolate push host creation/initialization behind explicit opt-in. +- [ ] `apps/client/test/integrations/mattermost_push_host_integration_test.dart`: no behavior change expected; run to confirm. +- [ ] Do not edit `apps/client/lib/src/integrations/mattermost/*` unless a compile error requires a narrow import/API update. + +#### 테스트 작성 + +No new Mattermost behavior test is needed beyond existing host tests, but run them because imports/factory usage may change. + +#### 중간 검증 + +Run from the remote ALT checkout root: + +```bash +cd apps/client && flutter test test/integrations/mattermost_push_host_integration_test.dart +``` + +Expected: exit code 0. + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `apps/client/lib/src/app/bootstrap.dart` | CLIENT-1, CLIENT-2, CLIENT-3 | +| `apps/client/lib/main.dart` | CLIENT-2 | +| `apps/client/lib/src/app/app.dart` | CLIENT-1 if nullable host docs/types need tightening | +| `apps/client/test/app/bootstrap_test.dart` | CLIENT-1, CLIENT-2 | +| `apps/client/test/widget_test.dart` | CLIENT-2 | +| `apps/client/test/integrations/mattermost_push_host_integration_test.dart` | CLIENT-3 verification only | + +## 최종 검증 + +Run from the remote ALT checkout root: + +```bash +cd apps/client && flutter test +``` + +Expected: exit code 0. Flutter test cache is not accepted as evidence; record actual stdout/stderr. + +If runtime socket smoke is attempted, use the client profile rules and record actual skip/blocker conditions instead of treating skipped integration as pass. + +모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/services/worker/internal/socket/handlers.go b/services/worker/internal/socket/handlers.go index 3411a2a..084f027 100644 --- a/services/worker/internal/socket/handlers.go +++ b/services/worker/internal/socket/handlers.go @@ -16,7 +16,7 @@ type sessionHandler struct { // through the same registry instead of bespoke registration paths. func sessionHandlers(deps Deps) []sessionHandler { handlers := []sessionHandler{ - helloHandler(), + helloHandler(deps), } handlers = append(handlers, marketHandlers(deps)...) handlers = append(handlers, backtestHandlers(deps)...) @@ -41,16 +41,21 @@ func registerHandlers(client *protoSocket.WsClient, handlers []sessionHandler) { } } -func helloHandler() sessionHandler { +func helloHandler(deps Deps) sessionHandler { return sessionHandler{ requestType: protoSocket.TypeNameOf(&altv1.HelloRequest{}), register: func(client *protoSocket.WsClient) { - protoSocket.AddRequestListenerTyped[*altv1.HelloRequest, *altv1.HelloResponse](&client.Communicator, handleHello) + protoSocket.AddRequestListenerTyped[*altv1.HelloRequest, *altv1.HelloResponse]( + &client.Communicator, + func(req *altv1.HelloRequest) (*altv1.HelloResponse, error) { + return handleHello(deps, req) + }, + ) }, } } -func handleHello(req *altv1.HelloRequest) (*altv1.HelloResponse, error) { +func handleHello(deps Deps, req *altv1.HelloRequest) (*altv1.HelloResponse, error) { protocolVersion := req.GetAltProtocolVersion() if protocolVersion == "" { protocolVersion = defaultAltProtocolVersion @@ -59,10 +64,20 @@ func handleHello(req *altv1.HelloRequest) (*altv1.HelloResponse, error) { ServerName: serverName, ServerVersion: serverVersion, AltProtocolVersion: protocolVersion, - Capabilities: []string{ - "hello", - "market-read", - "worker-execution", - }, + Capabilities: capabilitiesForDeps(deps), }, nil } + +func capabilitiesForDeps(deps Deps) []string { + caps := []string{"hello"} + if deps.Instruments != nil && deps.Bars != nil { + caps = append(caps, "market-read") + } + if deps.Analysis != nil && deps.Results != nil { + caps = append(caps, "backtest-read") + } + if deps.Starter != nil { + caps = append(caps, "backtest-start", "worker-execution") + } + return caps +} diff --git a/services/worker/internal/socket/server_test.go b/services/worker/internal/socket/server_test.go index 3846ea7..1f1542e 100644 --- a/services/worker/internal/socket/server_test.go +++ b/services/worker/internal/socket/server_test.go @@ -62,6 +62,13 @@ func TestWorkerSocketServerHello(t *testing.T) { if res.AltProtocolVersion != "alt.v1" { t.Errorf("expected AltProtocolVersion to be %q, got %q", "alt.v1", res.AltProtocolVersion) } + + // Assert no backtest-start or worker-execution since BacktestDeps{} has no Starter. + for _, cap := range res.Capabilities { + if cap == "backtest-start" || cap == "worker-execution" { + t.Errorf("expected capability %q to be omitted when Starter is nil", cap) + } + } } func TestSessionHandlers(t *testing.T) { @@ -104,3 +111,74 @@ func TestRegisterHandlers(t *testing.T) { registerHandlers(nil, handlers) }) } + +func TestWorkerHelloCapabilitiesReflectDeps(t *testing.T) { + tests := []struct { + name string + deps Deps + expected []string + }{ + { + name: "no deps", + deps: Deps{}, + expected: []string{"hello"}, + }, + { + name: "market deps", + deps: Deps{ + Instruments: &fakeInstrumentStore{}, + Bars: &fakeBarStore{}, + }, + expected: []string{"hello", "market-read"}, + }, + { + name: "backtest read deps", + deps: Deps{ + Analysis: &fakeAnalysisStore{}, + Results: &fakeResultStore{}, + }, + expected: []string{"hello", "backtest-read"}, + }, + { + name: "starter deps", + deps: Deps{ + Starter: &fakeStarter{}, + }, + expected: []string{"hello", "backtest-start", "worker-execution"}, + }, + { + name: "all deps", + deps: Deps{ + Instruments: &fakeInstrumentStore{}, + Bars: &fakeBarStore{}, + Analysis: &fakeAnalysisStore{}, + Results: &fakeResultStore{}, + Starter: &fakeStarter{}, + }, + expected: []string{"hello", "market-read", "backtest-read", "backtest-start", "worker-execution"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + caps := capabilitiesForDeps(tt.deps) + if len(caps) != len(tt.expected) { + t.Fatalf("expected caps %v, got %v", tt.expected, caps) + } + for i, v := range tt.expected { + if caps[i] != v { + t.Errorf("expected cap at %d to be %q, got %q", i, v, caps[i]) + } + } + }) + } +} + +func TestWorkerCapabilitiesDoNotClaimUnavailableStart(t *testing.T) { + caps := capabilitiesForDeps(Deps{}) + for _, cap := range caps { + if cap == "backtest-start" || cap == "worker-execution" { + t.Errorf("capabilities for empty Deps{} should not claim unavailable start, but got %q", cap) + } + } +}