<!-- task=m-api-centered-proto-socket-rail/02+01_worker_socket_rail plan=1 tag=REVIEW_WORKER -->
# PLAN-cloud-G08: Worker Socket Rail Review Fixes

## 이 파일을 읽는 구현 에이전트에게
이 계획은 이전 `WORKER` 구현의 코드리뷰 FAIL 항목만 고친다. 구현 후 `CODE_REVIEW-cloud-G08.md`의 구현 에이전트 소유 섹션과 `검증 결과`에 실제 stdout/stderr를 붙이고 active 파일을 그대로 둔 채 리뷰를 요청한다. finalization, log rename, `complete.log`, task archive는 code-review 전용이다. 사용자만 결정할 수 있는 범위 충돌이나 외부 환경 준비가 필요하면 review stub의 `사용자 리뷰 요청`을 증거와 함께 채우고 멈춘다. 검증 증거 공백은 사용자 리뷰 요청이 아니며, 가능한 후속 agent가 명령을 재실행해 채워야 한다.

## 배경
이전 구현은 API와 worker 사이의 proto-socket rail을 추가했지만, `WorkerClient.Hello`가 `context.Context` 취소를 요청 생명주기에 충분히 반영하지 않는다. worker module manifest와 worker handler registration 테스트도 계획 요구를 채우지 못했다. 또한 review file에는 `bin/test` 성공 서술만 있고 실제 stdout/stderr가 없어 검증 신뢰성이 없다.

## 사용자 리뷰 요청 흐름
구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없을 때만 `CODE_REVIEW-cloud-G08.md`의 `사용자 리뷰 요청` 섹션을 채운다. code-review가 그 요청을 검증하고 필요할 때만 `USER_REVIEW.md`를 작성한다.

## 분석 결과

### 읽은 파일
- `agent-task/m-api-centered-proto-socket-rail/02+01_worker_socket_rail/plan_cloud_G08_0.log`
- `agent-task/m-api-centered-proto-socket-rail/02+01_worker_socket_rail/code_review_cloud_G08_0.log`
- `services/api/internal/workerclient/client.go`
- `services/api/internal/workerclient/client_test.go`
- `services/api/cmd/alt-api/main.go`
- `services/api/internal/socket/server.go`
- `services/api/internal/socket/handlers.go`
- `services/api/internal/socket/server_test.go`
- `services/api/internal/config/config.go`
- `services/api/internal/config/config_test.go`
- `services/api/internal/contracts/parser_map.go`
- `services/api/internal/contracts/parser_map_test.go`
- `services/api/go.mod`
- `services/worker/cmd/alt-worker/main.go`
- `services/worker/internal/config/config.go`
- `services/worker/internal/config/config_test.go`
- `services/worker/internal/socket/server.go`
- `services/worker/internal/socket/handlers.go`
- `services/worker/internal/socket/server_test.go`
- `services/worker/internal/contracts/parser_map.go`
- `services/worker/internal/contracts/parser_map_test.go`
- `services/worker/internal/jobs/runner.go`
- `services/worker/internal/jobs/backtest_jobs.go`
- `services/worker/internal/storage/ports.go`
- `services/worker/go.mod`
- `../proto-socket/go/ws_server.go`
- `../proto-socket/go/ws_client.go`
- `../proto-socket/go/communicator.go`

### 테스트 커버리지 공백
- `WorkerClient.Hello`의 이미 취소된 context, 이미 만료된 deadline, in-flight cancellation 동작이 테스트되지 않았다.
- worker `sessionHandlers()`의 필수 request type 포함 여부와 중복 등록 방지 테스트가 없다.
- worker module manifest가 새 direct imports를 담는지 검증하는 확인 절차가 없다.
- 이전 review file은 실제 stdout/stderr를 남기지 않아 `bin/test` 성공 주장을 검증할 수 없다.

### 심볼 참조
- renamed/removed symbol: 없음.
- `WorkerClient.Hello` call sites: `services/api/internal/workerclient/client_test.go:78`, `services/api/internal/workerclient/client_test.go:114`.
- `socket.Worker` call sites: `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`.

### 분할 판단
split decision policy를 다시 확인했다. 이번 후속은 이전 `02+01_worker_socket_rail`의 FAIL 보완만 다루며 코드, 테스트, manifest, 검증 증거가 같은 rail의 완료 조건에 묶여 있다. 새 subtask로 나누면 같은 Required 이슈를 두 task에 나누게 되므로 기존 active task 디렉터리 안에서 단일 follow-up plan으로 처리한다.

### 범위 결정 근거
- backtest/market business handler wiring은 후속 `04+02_backtest_rail`, `05+02_market_rail` 범위라 수정하지 않는다.
- API client-facing handler 추가는 이 보완의 목표가 아니므로 `services/api/internal/socket/handlers.go`에는 필요한 경우 테스트 영향만 반영한다.
- `../proto-socket/go`는 sibling dependency로 읽기만 했고 수정하지 않는다.
- roadmap 상태 갱신과 archive는 code-review/runtime 책임이므로 이 계획에서 수행하지 않는다.

### 빌드 등급
- Build lane: `cloud-G08`
- Review lane: `cloud-G08`
- 근거: proto-socket API/worker process boundary, module manifest, verification trust 회복을 함께 다루는 follow-up이며 이전 리뷰가 verification trust Fail을 냈다.

## 구현 체크리스트
- [ ] [REVIEW_WORKER-1] `WorkerClient.Hello`가 이미 취소된 context, 만료된 deadline, 요청 중 취소를 timeout/unavailable 계약에 맞게 처리하고 regression test를 추가한다.
- [ ] [REVIEW_WORKER-2] worker module manifest에 새 direct imports와 proto-socket local replace를 반영하고 manifest 검증을 기록한다.
- [ ] [REVIEW_WORKER-3] worker socket handler registration 테스트를 추가해 필수 request type과 중복 등록 방지를 검증한다.
- [ ] [REVIEW_WORKER-4] 허용된 검증 환경에서 필수 명령을 실행하고 실제 stdout/stderr를 `CODE_REVIEW-cloud-G08.md`에 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_WORKER-1] WorkerClient context cancellation 복구
문제:
`services/api/internal/workerclient/client.go:101`의 `Hello`는 시작 시 `ctx.Err()`를 확인하지 않고, 요청 중 `ctx.Done()`도 select하지 않는다. 이미 만료된 deadline은 `time.Until(dl) <= 0`으로 계산된 뒤 proto-socket `SendRequest` 내부에서 기본 30초 timeout으로 바뀔 수 있다.

해결 방법:
Before (`services/api/internal/workerclient/client.go:101`):
```go
timeout := 5 * time.Second
if dl, ok := ctx.Deadline(); ok {
	timeout = time.Until(dl)
}

res, err := protoSocket.SendRequestTyped[*altv1.HelloRequest, *altv1.HelloResponse](&client.Communicator, req, timeout)
```

After:
```go
if err := ctx.Err(); err != nil {
	return nil, fmt.Errorf("%w: %v", ErrTimeout, err)
}
timeout := 5 * time.Second
if dl, ok := ctx.Deadline(); ok {
	timeout = time.Until(dl)
	if timeout <= 0 {
		return nil, fmt.Errorf("%w: %v", ErrTimeout, ctx.Err())
	}
}
```
그 뒤 요청 실행은 `ctx.Done()`과 결과 channel을 select해 in-flight cancellation도 즉시 반환하게 한다. proto-socket이 context를 직접 받지 않으므로 goroutine은 bounded timeout으로 종료되게 유지한다.

수정 파일 및 체크리스트:
- `services/api/internal/workerclient/client.go`
  - [ ] 시작 전 취소/만료 deadline을 즉시 반환한다.
  - [ ] 요청 중 취소를 `ctx.Done()`으로 관찰한다.
  - [ ] 기존 `ErrUnavailable`, `ErrTimeout` wrapping 의미를 유지한다.
- `services/api/internal/workerclient/client_test.go`
  - [ ] `TestWorkerClient_Hello_ContextAlreadyCanceled`를 추가한다.
  - [ ] `TestWorkerClient_Hello_ExpiredDeadline`을 추가한다.
  - [ ] `TestWorkerClient_Hello_CancelDuringRequest`를 추가한다.

테스트 작성:
- 위 세 테스트를 추가한다. fake worker가 지연 응답하는 fixture는 기존 `startFakeWorker`를 재사용한다.

중간 검증:
- 원격 검증 환경에서만 `go test -count=1 ./services/api/internal/workerclient -run 'TestWorkerClient_Hello'` 실행. 기대 결과: exit code 0과 해당 package `ok`.

### [REVIEW_WORKER-2] worker module manifest 보완
문제:
`services/worker/go.mod:5`는 worker socket 구현이 새로 import하는 contract/proto-socket/websocket/protobuf direct dependency를 담지 않는다. root `go.work`가 일부를 숨길 수 있어도 service module metadata가 계획 대비 불완전하다.

해결 방법:
`services/worker/go.mod`에 다음 direct requirements를 반영한다.
```go
git.toki-labs.com/toki/alt/packages/contracts/gen/go v0.0.0-20260527202903-88c673d97307
git.toki-labs.com/toki/proto-socket/go v0.0.0
nhooyr.io/websocket v1.8.17
google.golang.org/protobuf v1.36.5
```
`proto-socket/go`에는 `services/api/go.mod`와 같은 sibling replace를 추가한다.

수정 파일 및 체크리스트:
- `services/worker/go.mod`
  - [ ] direct imports가 require block에 들어간다.
  - [ ] `replace git.toki-labs.com/toki/proto-socket/go => ../../../proto-socket/go`가 들어간다.
- `services/worker/go.sum`
  - [ ] manifest 갱신으로 필요한 checksum 변화가 있으면 함께 반영한다.

테스트 작성:
- 별도 unit test는 작성하지 않는다. module manifest 보완은 deterministic manifest check와 worker package test로 검증한다.

중간 검증:
- `rg --sort path -n 'git.toki-labs.com/toki/alt/packages/contracts/gen/go|git.toki-labs.com/toki/proto-socket/go|nhooyr.io/websocket|google.golang.org/protobuf|replace git.toki-labs.com/toki/proto-socket/go' services/worker/go.mod` 실행. 기대 결과: 다섯 항목이 모두 출력된다.

### [REVIEW_WORKER-3] worker handler registration 테스트 추가
문제:
`services/worker/internal/socket/server_test.go:15`는 live hello request-response만 검증한다. 계획의 "worker socket handler registration test" 요구인 필수 request type 등록과 중복 방지 검증이 없다.

해결 방법:
API socket test의 구조를 worker package에 맞게 이식한다. `sessionHandlers()`를 직접 검사해 `HelloRequest`가 포함되는지, request type이 비어 있거나 중복되지 않는지 확인한다.

수정 파일 및 체크리스트:
- `services/worker/internal/socket/server_test.go`
  - [ ] `TestSessionHandlersHaveUniqueRequestTypes`를 추가한다.
  - [ ] `TestSessionHandlersCoverRequiredRequests`를 추가한다.
  - [ ] 필요하면 nil registrar skip 테스트를 worker registry에도 추가한다.

테스트 작성:
- 위 테스트를 작성한다. 필수 request list는 현재 rail 범위의 `altv1.HelloRequest{}`만 포함한다.

중간 검증:
- 원격 검증 환경에서만 `go test -count=1 ./services/worker/internal/socket -run 'Test(SessionHandlers|RegisterHandlers|WorkerSocketServerHello)'` 실행. 기대 결과: exit code 0과 해당 package `ok`.

### [REVIEW_WORKER-4] 검증 증거 복구
문제:
`code_review_cloud_G08_0.log:20`은 `bin/test` 통과를 서술했지만 실제 stdout/stderr가 없다. 현재 로컬 테스트 규칙은 로컬 실행을 금지하므로 허용된 원격 검증 환경에서 명령 출력 자체를 기록해야 한다.

해결 방법:
새 `CODE_REVIEW-cloud-G08.md`의 `검증 결과`에 아래 최종 검증 명령의 실제 stdout/stderr를 붙인다. 명령을 대체하면 `계획 대비 변경 사항`에 이유와 대체 명령을 남긴다.

수정 파일 및 체크리스트:
- `agent-task/m-api-centered-proto-socket-rail/02+01_worker_socket_rail/CODE_REVIEW-cloud-G08.md`
  - [ ] 각 중간 검증 출력이 실제 command block으로 들어간다.
  - [ ] 최종 검증 세 명령의 stdout/stderr가 요약이 아닌 원문으로 들어간다.
  - [ ] 로컬에서 실행하지 못한 경우에는 로컬 테스트 금지 규칙을 생략 사유로 쓰고, 원격 검증 출력이 없으면 구현 완료로 표시하지 않는다.

테스트 작성:
- 테스트 파일 추가가 아니라 evidence recovery 작업이다.

중간 검증:
- `rg --sort path -n 'go test -count=1 ./services/api|go test -count=1 ./services/worker|bin/contracts-check|stdout|stderr' agent-task/m-api-centered-proto-socket-rail/02+01_worker_socket_rail/CODE_REVIEW-cloud-G08.md` 실행. 기대 결과: review stub에 실제 검증 block이 남아 있음을 확인할 수 있다.

## 수정 파일 요약
| 파일 | 항목 |
|------|------|
| `services/api/internal/workerclient/client.go` | REVIEW_WORKER-1 |
| `services/api/internal/workerclient/client_test.go` | REVIEW_WORKER-1 |
| `services/worker/go.mod` | REVIEW_WORKER-2 |
| `services/worker/go.sum` | REVIEW_WORKER-2 |
| `services/worker/internal/socket/server_test.go` | REVIEW_WORKER-3 |
| `agent-task/m-api-centered-proto-socket-rail/02+01_worker_socket_rail/CODE_REVIEW-cloud-G08.md` | REVIEW_WORKER-4 |

## 최종 검증
- 원격 검증 환경에서만 `go test -count=1 ./services/api/...`
- 원격 검증 환경에서만 `go test -count=1 ./services/worker/...`
- 원격 검증 환경에서만 `bin/contracts-check`
- 로컬에서는 `agent-test/local/rules.md`의 "현재 로컬에서의 테스트는 절대 금지" 규칙 때문에 위 테스트/검증 명령을 실행하지 않는다.
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
