<!-- task=m-architecture-refactor-foundation/03+02_openai_surface_split plan=0 tag=REFACTOR -->

# OpenAI Surface Split Plan

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

`CODE_REVIEW-cloud-G05.md`의 구현 에이전트 소유 섹션 작성까지가 구현의 일부다. 코드 변경 후 검증을 실행하고 실제 변경 내용, 검증 출력, 계획 대비 변경 사항을 채운 뒤 active 파일을 그대로 두고 리뷰 준비를 보고한다. 사용자 결정, 사용자 소유 외부 환경, 범위 충돌 때문에 진행할 수 없으면 review stub의 `사용자 리뷰 요청`에 근거와 재개 조건을 적고 중단한다. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 최종 판정, log archive, `complete.log` 작성은 code-review 전용이다.

## 배경

`apps/edge/internal/openai/server.go`는 HTTP lifecycle, route handlers, chat request mapping, run result collection, SSE streaming, strict output normalization, Ollama passthrough DTO를 한 파일에 담고 있다. OpenAI-compatible 표면은 edge inbound adapter여야 하며 IOP native 기능을 흡수하지 않아야 한다. 이 작업은 service split 완료 후 OpenAI package 내부 파일 경계를 나누고, behavior는 기존 tests로 보존한다.

## 사용자 리뷰 요청 흐름

구현 중 blocker는 active `CODE_REVIEW-cloud-G05.md`의 `사용자 리뷰 요청` 섹션에 기록한다. 구현 에이전트는 직접 사용자 프롬프트를 만들지 않으며, code-review가 요청의 타당성을 판단하고 필요할 때만 `USER_REVIEW.md`를 작성한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/automation-runtime-bridge/milestones/architecture-refactor-foundation.md`
- Task ids:
  - `openai-surface-split`: `apps/edge/internal/openai`에서 HTTP lifecycle, chat request mapping, run stream/result collection, strict output policy, Ollama passthrough를 분리해 OpenAI-compatible 표면이 IOP native 기능을 흡수하지 않게 한다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/project/domain/edge/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-test/local/rules.md`
- `agent-test/local/edge-smoke.md`
- `agent-roadmap/current.md`
- `agent-roadmap/phase/automation-runtime-bridge/PHASE.md`
- `agent-roadmap/phase/automation-runtime-bridge/milestones/architecture-refactor-foundation.md`
- `apps/edge/internal/openai/server.go`
- `apps/edge/internal/openai/server_test.go`
- `apps/edge/internal/service/service.go`
- `apps/edge/internal/service/service_test.go`

### 테스트 환경 규칙

- `test_env=local`; `agent-test/local/rules.md`를 읽었다.
- matching profile: `agent-test/local/edge-smoke.md`.
- 적용 명령: OpenAI-compatible 경계 변경은 focused `go test ./apps/edge/internal/openai`와 최종 `go test ./apps/edge/...`를 실행한다.
- 실제 `iop-edge smoke openai`는 live edge/node endpoint가 필요해 이 리팩토링의 기본 필수 명령으로 두지 않는다. package tests use `httptest` and fake service.

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

- Existing tests cover configured target dispatch, request model fallback, Ollama options passthrough, SSE streaming, reasoning content, strict output normalization/wrapping/buffered streaming/live streaming, model list, Ollama API passthrough, collect timeout.
- HTTP lifecycle route registration is lightly covered through direct handler calls, not `Start`; implementation should avoid changing lifecycle behavior beyond moving code.

### 심볼 참조

- `NewServer` and `Start` are used by `apps/edge/internal/input/manager.go`.
- `handleChatCompletions`, `handleOllamaAPI`, `collectRunResult`, `streamChatCompletion`, and strict output helpers are package-private and used by `server.go`/`server_test.go`.
- No public API rename is planned. Package-private helpers may move files but should keep names unless tests are updated in the same package.

### 분할 판단

- shared task group: `agent-task/m-architecture-refactor-foundation/`.
- sibling subtasks: `01_edge_cmd_split` independent, `02_edge_service_split` independent, `03+02_openai_surface_split` depends on predecessor `02_edge_service_split`.
- predecessor resolution: `02_edge_service_split` has active plan files and no `complete.log` yet. Runtime must wait for `agent-task/m-architecture-refactor-foundation/02_edge_service_split/complete.log` or its archived equivalent before implementing this plan.

### 범위 결정 근거

- Do not add Responses API, A2A streaming, new provider quality behavior, or native terminal control to OpenAI-compatible routes.
- Do not change `edgeservice.RunHandle` semantics in this task; consume the service API produced by `02_edge_service_split`.
- Do not modify proto/config contracts.

### 빌드 등급

- `cloud-G05`: user-visible HTTP/SSE behavior and strict output normalization are easy to regress despite package-only refactor, so review needs careful semantic comparison.

## 구현 체크리스트

- [ ] Confirm predecessor `02_edge_service_split` has `complete.log` before implementation; do not start from this plan while predecessor is active/incomplete.
- [ ] Split `apps/edge/internal/openai/server.go` into lifecycle/routes, chat request mapping, run collection/SSE streaming, strict output policy, Ollama passthrough, HTTP response/types files while keeping package API stable.
- [ ] Keep OpenAI-compatible behavior unchanged: request validation, model target resolution, strict output policy, SSE chunks, Ollama passthrough status/content-type/body.
- [ ] `go test ./apps/edge/internal/openai`와 `go test ./apps/edge/...`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

- Directory dependency source: `03+02_openai_surface_split` depends on sibling index `02`.
- Required predecessor: `agent-task/m-architecture-refactor-foundation/02_edge_service_split/complete.log` or matching archived `complete.log`.
- Current status at plan creation: missing; predecessor has active plan/review files only.

### [REFACTOR-1] Split OpenAI-Compatible Surface Responsibilities

#### 문제

- [server.go](/config/workspace/iop/apps/edge/internal/openai/server.go:45)는 lifecycle and route registration을 소유하고, 같은 파일의 [server.go](/config/workspace/iop/apps/edge/internal/openai/server.go:119)는 chat request mapping and dispatch, [server.go](/config/workspace/iop/apps/edge/internal/openai/server.go:259)는 SSE streaming, [server.go](/config/workspace/iop/apps/edge/internal/openai/server.go:676)는 strict output normalization을 함께 담는다.
- [server.go](/config/workspace/iop/apps/edge/internal/openai/server.go:191)의 Ollama passthrough도 같은 file에 있어 OpenAI-compatible adapter 책임이 커지고 있다.

#### 해결 방법

Before:

```go
// apps/edge/internal/openai/server.go:45
func (s *Server) Start(ctx context.Context) error {
    mux := http.NewServeMux()
    mux.HandleFunc("/healthz", s.handleHealthz)
    mux.HandleFunc("/v1/models", s.handleModels)
    mux.HandleFunc("/v1/chat/completions", s.handleChatCompletions)
    mux.HandleFunc("/api/", s.handleOllamaAPI)
    ...
}
```

After:

```go
// apps/edge/internal/openai/server.go
func (s *Server) Start(ctx context.Context) error {
    mux := s.routes()
    return s.serve(ctx, mux)
}

// apps/edge/internal/openai/routes.go
func (s *Server) routes() *http.ServeMux {
    mux := http.NewServeMux()
    mux.HandleFunc("/healthz", s.handleHealthz)
    mux.HandleFunc("/v1/models", s.handleModels)
    mux.HandleFunc("/v1/chat/completions", s.handleChatCompletions)
    mux.HandleFunc("/api/", s.handleOllamaAPI)
    return mux
}
```

- `chat_handler.go`: request validation, prompt mapping, `SubmitRun`.
- `run_result.go`: `collectRunResult`, timeout/error mapping.
- `stream.go`: SSE response writing and streaming collectors.
- `strict_output.go`: XML/strict policy inference and normalization.
- `ollama_passthrough.go`: `/api/` passthrough.
- `types.go`: OpenAI request/response DTOs.
- Keep tests in package `openai`; split test files only if it improves review.

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

- [ ] `apps/edge/internal/openai/server.go`
- [ ] `apps/edge/internal/openai/routes.go`
- [ ] `apps/edge/internal/openai/chat_handler.go`
- [ ] `apps/edge/internal/openai/run_result.go`
- [ ] `apps/edge/internal/openai/stream.go`
- [ ] `apps/edge/internal/openai/strict_output.go`
- [ ] `apps/edge/internal/openai/ollama_passthrough.go`
- [ ] `apps/edge/internal/openai/types.go`
- [ ] `apps/edge/internal/openai/*_test.go`

#### 테스트 작성

- Existing tests should continue to cover behavior. Add route/lifecycle-focused tests only if extracting `routes()` changes route registration in a way direct handler tests cannot catch.

#### 중간 검증

```bash
go test ./apps/edge/internal/openai
```

Expected: OpenAI package tests pass.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/edge/internal/openai/server.go` | REFACTOR-1 |
| `apps/edge/internal/openai/routes.go` | REFACTOR-1 |
| `apps/edge/internal/openai/chat_handler.go` | REFACTOR-1 |
| `apps/edge/internal/openai/run_result.go` | REFACTOR-1 |
| `apps/edge/internal/openai/stream.go` | REFACTOR-1 |
| `apps/edge/internal/openai/strict_output.go` | REFACTOR-1 |
| `apps/edge/internal/openai/ollama_passthrough.go` | REFACTOR-1 |
| `apps/edge/internal/openai/types.go` | REFACTOR-1 |
| `apps/edge/internal/openai/*_test.go` | REFACTOR-1 |

## 최종 검증

```bash
go test ./apps/edge/...
```

Expected: all edge package tests pass. Go test cache output is acceptable for unchanged packages; use `-count=1` if strict output or SSE tests appear stale.

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