<!-- task=m-core-server-boundary-cleanup/03+01,02_handlers plan=1 tag=HANDLERS -->

# Implementation Plan - HANDLERS

## 이 파일을 읽는 구현 에이전트에게

구현 완료의 마지막 단계는 active `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우는 것이다. 검증을 실행하고, active 파일은 그대로 둔 채 리뷰 준비 상태로 보고한다. 종결 처리, log archive, `complete.log` 작성, task directory archive 이동은 code-review 전용이다. 구현 중 사용자 결정, 사용자 소유 외부 환경, scope conflict 없이는 진행할 수 없으면 리뷰 stub의 `사용자 리뷰 요청`에 근거를 기록하고 멈춘다. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다.

## 배경

`server.go`에는 runner registry, bootstrap, job/execution, log/artifact, runner claim/report/cancel/status/self-update handlers가 함께 있다. routes와 DTO 경계가 선행 분리되면 handler body를 기능별 파일로 옮겨도 API 동작을 유지하기 쉽다. 이 plan은 handler 책임 파일 경계만 정리하고 endpoint 의미는 바꾸지 않는다.

## 사용자 리뷰 요청 흐름

구현 중 blocker는 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 이 섹션은 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 기준이다. 구현 에이전트는 직접 사용자 prompt를 만들지 않으며, code-review가 사용자 리뷰 요청의 타당성 검증과 실제 `USER_REVIEW.md` 작성을 담당한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/independent-control-plane/milestones/core-server-boundary-cleanup.md`
- Task ids:
  - `handlers`: runner, job/execution, log/artifact handler 책임을 기능별 파일 또는 내부 모듈로 나눈다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-roadmap/current.md`
- `agent-roadmap/phase/independent-control-plane/PHASE.md`
- `agent-roadmap/phase/independent-control-plane/milestones/core-server-boundary-cleanup.md`
- `agent-test/local/rules.md`
- `services/core/internal/httpserver/server.go`
- `services/core/internal/httpserver/server_test.go`
- `services/core/internal/cicdstate/store.go`
- `services/core/internal/cicdstate/store_test.go`
- `Makefile`
- `services/core/go.mod`

### 테스트 환경 규칙

- 선택 test_env: `local`.
- `agent-test/local/rules.md` 존재하고 읽음.
- `services/core` 전용 smoke 문서는 없음. local 규칙의 Monorepo Targets 기준으로 `make core-test` 또는 `cd services/core && go test ./...`를 적용한다.
- `<확인 필요>` 값은 없었다.
- fallback 검증 source: root `Makefile`의 `core-test` target.
- Go test cache는 허용하지 않는다. 최종 검증은 `cd services/core && go test -count=1 ./...`를 사용한다.

### 테스트 커버리지 공백

- handler 분리는 내부 파일 이동이며 endpoint behavior 변경은 없다.
- runner registry/bootstrap tests: `TestHandleRunnerRegisterAcceptsAndStoresRunner`, `TestHandleRunnerRegisterRejectsMissingToken`, `TestHandleRunnerHeartbeat`, `TestHandleRunnerDisconnect`, `TestHandleGetRunner`, `TestHandleRunnerBootstrapCommand`.
- job/execution/log/artifact tests: `TestHandleCreateJob`, `TestHandleCreateJobStoresRunRequest`, `TestHandleCreateExecution`, `TestHandleExecutionLogsAndArtifacts`, `TestHandleCicdUnknownIds`, `TestHandleCicdMethodNotAllowed`, `TestHandleCicdNotFound`.
- runner execution lifecycle tests: `TestHandleRunnerCicdClaimReturnsRunRequest`, `TestHandleRunnerCicdNextJobClaim`, `TestHandleRunnerCicdRejectsInvalidRunInput`, `TestHandleRunnerCicdClaimReportLogsAndArtifacts`, `TestHandleRunnerCicdRejectsUnknownRunner`, `TestHandleRunnerCancelExecution`, `TestHandleRunnerStatus`, `TestHandleRunnerSelfUpdate`.
- 신규 behavior가 없으므로 신규 테스트는 쓰지 않는다. 이동 중 helper visibility 오류는 package tests로 잡는다.

### 심볼 참조

- 제거/rename 대상 없음.
- 이동 대상 handlers:
  - health/bootstrap/basic runner: `handleHealthz`, `handleReadyz`, `handleRunnerRegister`, `handleRunnerHeartbeat`, `handleRunnerDisconnect`, `handleGetRunner`, `handleRunnerBootstrapCommand`, `handleServeBootstrapScript`.
  - job/execution HTTP: `handleCreateJob`, `handleGetJob`, `handleCreateExecution`, `handleGetExecution`, `handleAppendLog`, `handleGetLogs`, `handleAppendArtifact`, `handleGetArtifacts`.
  - runner lifecycle: `handleRunnerClaimJob`, `handleRunnerReportExecution`, `handleRunnerAppendLog`, `handleRunnerAppendArtifact`, `handleRunnerCancelExecution`, `handleRunnerStatus`, `handleRunnerSelfUpdate`, `normalizeRunnerExecutionRequest`, `ensureRunnerKnown`.
- route call sites are in `routes.go` after predecessor `01_routes`; direct test call sites remain in `server_test.go`.

### 분할 판단

- split decision policy를 적용했다.
- shared task group: `agent-task/m-core-server-boundary-cleanup`.
- sibling plans:
  - `01_routes`: dependencies: none.
  - `02+01_dto`: depends_on: `01_routes`.
  - `03+01,02_handlers`: depends_on: `01_routes`, `02+01_dto`.
- 이 plan의 predecessors:
  - `01_routes`: 현재 active directory가 있으나 `complete.log` 없음.
  - `02+01_dto`: 현재 active directory가 있으나 `complete.log` 없음.
- 구현 시작 전 두 predecessor의 active 또는 archived `complete.log`가 필요하다.

### 범위 결정 근거

- 이 plan은 handler body의 파일 배치만 바꾼다.
- route table 변경, DTO response shape 변경, store state transition 변경, endpoint auth/storage 추가는 제외한다.
- handler grouping은 package-local files로 제한하고 새 package를 만들지 않는다. Go internal import 경계를 흔들지 않기 위해 같은 package 유지가 표준선이다.

### 빌드 등급

- build: `local-G07`, review: `local-G07`.
- 이유: 파일 이동 자체는 기계적이지만 handler 수와 call site가 많아 bounded yet careful local review와 full fresh tests가 필요하다.

## 의존 관계 및 구현 순서

- 이 subtask dir `03+01,02_handlers`는 predecessor index `01`, `02`에 의존한다.
- 구현 전 아래 중 하나가 필요하다.
  - `agent-task/m-core-server-boundary-cleanup/01_routes/complete.log` 또는 matching archive complete.log.
  - `agent-task/m-core-server-boundary-cleanup/02+01_dto/complete.log` 또는 matching archive complete.log.
- 현재 predecessor 상태: both missing complete.log.

## 구현 체크리스트

- [ ] predecessor `01_routes`와 `02+01_dto`의 `complete.log` 존재를 확인한다.
- [ ] runner registry/bootstrap handlers를 `runner_handlers.go` 또는 역할이 더 명확한 파일로 이동한다.
- [ ] job/execution/log/artifact handlers를 `job_handlers.go`와 `execution_handlers.go`로 나눈다.
- [ ] runner claim/report/cancel/status/self-update handlers를 `runner_cicd_handlers.go`로 이동한다.
- [ ] `server.go`는 Server type, constructors, Start/Shutdown 같은 server lifecycle만 소유하게 정리한다.
- [ ] `gofmt`를 실행하고 `cd services/core && go test -count=1 ./internal/httpserver`를 통과시킨다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [HANDLERS-1] Handler File Boundary

#### 문제

- `services/core/internal/httpserver/server.go:73-400`에 health, runner registry, bootstrap handlers가 server lifecycle과 함께 있다.
- `services/core/internal/httpserver/server.go:491-699`에 job/execution/log/artifact handlers가 있다.
- `services/core/internal/httpserver/server.go:702-1014`와 `1155-1323`에 runner claim/report/cancel/status/self-update handlers가 있다.

Before:

```go
// services/core/internal/httpserver/server.go:91
func handleRunnerRegister(registry *runnerregistry.Registry) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
			return
		}
```

```go
// services/core/internal/httpserver/server.go:491
func handleCreateJob(store *cicdstate.Store) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		var req struct {
			ID         string            `json:"id"`
			Name       string            `json:"name"`
			RunRequest *otopb.RunRequest `json:"run_request"`
```

```go
// services/core/internal/httpserver/server.go:702
func handleRunnerClaimJob(store *cicdstate.Store, registry *runnerregistry.Registry, runnerID string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		var req otopb.JobClaimRequest
```

#### 해결 방법

- 같은 package 안에서 파일만 나눈다. 새 package는 만들지 않는다.
- 제안 파일:
  - `runner_handlers.go`: registry, heartbeat, disconnect, get runner, bootstrap command/script.
  - `job_handlers.go`: create/get job, create execution.
  - `execution_handlers.go`: get execution, append/get logs, append/get artifacts.
  - `runner_cicd_handlers.go`: claim/report/log/artifact/cancel/status/self-update and runner execution request normalization.
- `server.go`에는 `Server`, constructors, lifecycle methods만 남긴다.

After:

```go
// services/core/internal/httpserver/server.go
type Server struct {
	httpServer *http.Server
}

func NewServerWithRegistryAndStore(addr string, registry *runnerregistry.Registry, store *cicdstate.Store) *Server {
	mux := http.NewServeMux()
	registerRoutes(mux, registry, store)
	return &Server{httpServer: &http.Server{Addr: addr, Handler: mux}}
}
```

#### 수정 파일 및 체크리스트

- [ ] `services/core/internal/httpserver/runner_handlers.go` 추가.
- [ ] `services/core/internal/httpserver/job_handlers.go` 추가.
- [ ] `services/core/internal/httpserver/execution_handlers.go` 추가.
- [ ] `services/core/internal/httpserver/runner_cicd_handlers.go` 추가.
- [ ] `services/core/internal/httpserver/server.go`에서 이동한 handler block 제거.
- [ ] `services/core/internal/httpserver/server_test.go` 기존 tests 유지. 컴파일에 필요한 symbol 참조만 최소 수정.

#### 테스트 작성

- 신규 테스트 작성 생략. 기존 handler tests가 각 endpoint behavior를 덮고, 이 plan은 파일 경계 리팩터다.

#### 중간 검증

```bash
cd services/core && go test -count=1 ./internal/httpserver
```

기대 결과: `ok github.com/toki/oto/services/core/internal/httpserver`.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `services/core/internal/httpserver/server.go` | HANDLERS-1 |
| `services/core/internal/httpserver/runner_handlers.go` | HANDLERS-1 |
| `services/core/internal/httpserver/job_handlers.go` | HANDLERS-1 |
| `services/core/internal/httpserver/execution_handlers.go` | HANDLERS-1 |
| `services/core/internal/httpserver/runner_cicd_handlers.go` | HANDLERS-1 |
| `services/core/internal/httpserver/server_test.go` | HANDLERS-1 |

## 최종 검증

```bash
cd services/core && go test -count=1 ./...
```

기대 결과: 모든 `services/core` package test 통과. Go test cache output은 허용하지 않는다.

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
