<!-- task=m-core-server-boundary-cleanup/02+01_dto plan=1 tag=DTO -->

# Implementation Plan - DTO

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

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

## 배경

HTTP DTO 변환 helper가 `server.go` 하단에 handler 구현과 함께 있다. DTO helper는 protobuf/request conversion과 JSON response shape를 소유하므로 handler 파일 분리 전에 별도 파일로 떼어내면 이후 변경 범위가 선명해진다. API response field와 protobuf mapping은 유지한다.

## 사용자 리뷰 요청 흐름

구현 중 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:
  - `dto`: HTTP DTO 변환과 domain state 조작 경계를 명확히 한다.
- 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 ./...`를 사용한다.

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

- DTO 분리는 JSON/protobuf field 의미를 바꾸지 않는 내부 리팩터다.
- `TestHandleCreateJobStoresRunRequest`가 create/get job run_request mapping을 확인한다.
- `TestHandleRunnerCicdClaimReturnsRunRequest`와 `TestHandleRunnerCicdClaimReportLogsAndArtifacts`가 stored RunInput과 claim response RunRequest mapping을 확인한다.
- `TestHandleExecutionLogsAndArtifacts`가 logs/artifacts JSON wrapper를 확인한다.
- 새 response shape를 만들지 않으므로 신규 테스트는 쓰지 않는다.

### 심볼 참조

- 제거/rename 대상 없음.
- 이동 대상 helper와 call site:
  - `jobToJSON`: `server.go:511`, `537`.
  - `runRequestFromJSON`: `server.go:506`.
  - `runRequestToProto`: `server.go:826`.
  - `runInputToJSON`: `server.go:1037`.
  - `execToJSON`: `server.go:567`, `589`.
  - `logsToJSON`: `server.go:643`.
  - `artifactsToJSON`: `server.go:698`.
  - `validateRunInput`: `server.go:783`.

### 분할 판단

- 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의 predecessor `01_routes`는 현재 active `agent-task/m-core-server-boundary-cleanup/01_routes/`가 있지만 `complete.log`가 없다. 구현 시작 전 `agent-task/m-core-server-boundary-cleanup/01_routes/complete.log` 또는 archive matching `01_*` complete.log가 필요하다.

### 범위 결정 근거

- 이 plan은 DTO/request conversion helper 이동과 작은 naming 정리만 다룬다.
- handler body 파일 분리, route switch 변경, endpoint 의미 변경, protobuf schema 변경은 제외한다.
- `jobResponse`, `execResponse`, `logEntryResponse`, `artifactResponse` unused type 제거 여부는 컴파일과 실제 사용 여부로 판단하되, response field 의미는 바꾸지 않는다.

### 빌드 등급

- build: `local-G06`, review: `local-G06`.
- 이유: DTO helper 이동은 범위가 좁지만 response shape 회귀가 중요해 fresh package tests가 필요하다.

## 의존 관계 및 구현 순서

- 이 subtask dir `02+01_dto`는 predecessor index `01`에 의존한다.
- 구현 전 `agent-task/m-core-server-boundary-cleanup/01_routes/complete.log` 또는 matching archive complete.log가 있어야 한다.
- 현재 predecessor 상태: missing complete.log.

## 구현 체크리스트

- [ ] predecessor `01_routes`의 `complete.log` 존재를 확인한다.
- [ ] `services/core/internal/httpserver/dto.go`를 추가하고 HTTP JSON/protobuf conversion helpers를 이동한다.
- [ ] `server.go`에는 handler/domain orchestration만 남기고 DTO helper 중복을 제거한다.
- [ ] response field names와 protobuf mapping을 기존 tests 기준으로 유지한다.
- [ ] `gofmt`를 실행하고 `cd services/core && go test -count=1 ./internal/httpserver`를 통과시킨다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [DTO-1] HTTP DTO Helper Boundary

#### 문제

- `services/core/internal/httpserver/server.go:1018-1147`에 response structs, JSON helpers, run request conversion, validation이 handler 구현과 같이 있다.
- `services/core/internal/httpserver/server.go:491-567`, `702-828`, `831-920`의 handlers가 store mutation과 DTO 변환을 같은 파일에서 수행한다.

Before:

```go
// services/core/internal/httpserver/server.go:1027
func jobToJSON(j *cicdstate.Job) map[string]interface{} {
	out := map[string]interface{}{
		"id":           j.ID,
		"name":         j.Name,
		"state":        j.State,
		"created_at":   j.CreatedAt.Format(time.RFC3339),
		"updated_at":   j.UpdatedAt.Format(time.RFC3339),
		"execution_id": j.ExecutionID,
	}
```

#### 해결 방법

- 같은 package의 `dto.go`로 DTO types/helpers를 이동한다.
- handler code는 기존 helper 이름을 계속 호출하게 두어 call site churn을 줄인다.
- unused DTO struct가 실제로 사용되지 않으면 제거하되, map response shape는 유지한다.

After:

```go
// services/core/internal/httpserver/dto.go
func jobToJSON(j *cicdstate.Job) map[string]interface{} {
	out := map[string]interface{}{
		"id":           j.ID,
		"name":         j.Name,
		"state":        j.State,
		"created_at":   j.CreatedAt.Format(time.RFC3339),
		"updated_at":   j.UpdatedAt.Format(time.RFC3339),
		"execution_id": j.ExecutionID,
	}
	if j.RunInput != nil {
		out["run_request"] = runInputToJSON(j.RunInput)
	}
	return out
}
```

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

- [ ] `services/core/internal/httpserver/dto.go` 추가.
- [ ] `services/core/internal/httpserver/server.go`에서 DTO helper block 제거.
- [ ] `services/core/internal/httpserver/server_test.go`는 기존 tests 유지. response shape가 깨지면 source를 고친다.

#### 테스트 작성

- 신규 테스트 작성 생략. 기존 tests가 response shape와 protobuf mapping을 확인한다.

#### 중간 검증

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

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

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `services/core/internal/httpserver/dto.go` | DTO-1 |
| `services/core/internal/httpserver/server.go` | DTO-1 |
| `services/core/internal/httpserver/server_test.go` | DTO-1 |

## 최종 검증

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

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

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