<!-- task=m-operator-runtime-refactor/01_worker_execution plan=0 tag=WORKER -->

# 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`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
