<!-- task=m-bridge-boundary-hardening/01_edge_run_surface plan=0 tag=API -->

# Plan - API

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

이 작업은 구현이 끝나도 active 파일을 유지하고, 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 변경 내용과 검증 출력으로 채운 뒤 리뷰 준비 상태로 멈춘다. 구현 중 사용자 결정, 사용자 소유 외부 환경, 또는 계획 범위 충돌 없이는 진행할 수 없으면 review stub의 `사용자 리뷰 요청` 섹션에 근거를 남기고 중단한다. 구현 중 사용자에게 직접 질문하거나 `USER_REVIEW.md`, `complete.log`, archive log를 만들지 않는다.

## 배경

Milestone의 `edge-run-surface` Task는 신규 native/terminal 표면이 legacy combined `RunHandle`에 기대지 않도록 Edge service 경계를 고정하는 작업이다. 현재 service에는 `RunDispatch`와 `RunStream`이 이미 있지만 OpenAI와 A2A interface, collector, tests는 여전히 `*RunHandle`을 직접 주고받는다. 이 plan은 서비스 반환 호환성은 유지하면서 신규 표면이 DTO를 직접 소비하는 경계를 만든다.

## 사용자 리뷰 요청 흐름

구현 중 차단은 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 직접 사용자 프롬프트는 금지하며, code-review가 요청 타당성을 검증하고 실제 `USER_REVIEW.md` 생성을 소유한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/automation-runtime-bridge/milestones/bridge-boundary-hardening.md`
- Task ids:
  - `edge-run-surface`: Edge 신규 native/terminal 표면은 legacy combined `RunHandle` 대신 `RunDispatch`와 `RunStream` 또는 동등한 surface-neutral 계약을 소비하도록 service interface와 테스트를 정리한다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-roadmap/current.md`
- `agent-roadmap/phase/automation-runtime-bridge/PHASE.md`
- `agent-roadmap/phase/automation-runtime-bridge/milestones/bridge-boundary-hardening.md`
- `agent-test/local/rules.md`
- `agent-test/local/edge-smoke.md`
- `agent-test/local/testing-smoke.md`
- `agent-ops/rules/project/domain/edge/rules.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `apps/edge/internal/service/run_dispatch.go`
- `apps/edge/internal/service/service.go`
- `apps/edge/internal/service/service_test.go`
- `apps/edge/internal/openai/server.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/server_test.go`
- `apps/edge/internal/input/a2a/server.go`
- `apps/edge/internal/input/a2a/task_store.go`
- `apps/edge/internal/input/a2a/server_test.go`
- `apps/edge/internal/input/a2a/task_store_test.go`

### 테스트 환경 규칙

- test_env: `local`.
- `agent-test/local/rules.md`를 읽었고, domain routing에 따라 `edge-smoke`, 사용자 실행 파이프라인 영향 기준으로 `testing-smoke`를 읽었다.
- 적용 명령: `go test -count=1 ./apps/edge/internal/service ./apps/edge/internal/openai ./apps/edge/internal/input/a2a`.
- 사전 확인 결과: 위 명령은 현재 checkout에서 성공했다.
- 실행 파이프라인 영향: service/openai/a2a run stream 경로 변경이므로 구현 후 보조 smoke 또는 full-cycle 실제 구동 수행 여부와 생략 사유를 review stub에 기록한다.

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

- OpenAI non-stream/stream/strict stream은 `apps/edge/internal/openai/server_test.go`가 기존 회귀를 보유한다.
- A2A blocking/background/cancel/task mapping은 `server_test.go`와 `task_store_test.go`가 보유한다.
- 현재 공백: OpenAI/A2A fake service interface가 `RunHandle` 반환을 허용해 신규 표면이 DTO 기반인지 직접 보장하지 않는다. 구현 중 fake interface와 collector signature를 DTO 기반으로 바꿔 회귀를 추가한다.

### 심볼 참조

- 변경 예정: `runService.SubmitRun` 반환 타입 또는 wrapper interface, `collectRunResult`, `streamChatCompletion`, `TaskStore.collect*`.
- 현재 `RunHandle` 참조: `rg --sort path "RunHandle" apps/edge/internal/service apps/edge/internal/openai apps/edge/internal/input/a2a`로 확인한 OpenAI/A2A/service/test call sites.

### 분할 판단

- split decision policy를 적용했다.
- 공유 task group: `m-bridge-boundary-hardening`.
- sibling subtasks:
  - `01_edge_run_surface`: Edge service/OpenAI/A2A surface-neutral run contract. 독립.
  - `02_terminal_core_boundary`: Node PTY terminal core boundary. 독립.
  - `03_adapter_config_typed`: proto/adapter config typed contract. 독립.
- 이 subtask는 다른 sibling의 `complete.log`를 필요로 하지 않는다.

### 범위 결정 근거

- 원격 터미널 bridge open/input/output/resize/signal/close 기능은 Milestone 범위 제외이므로 구현하지 않는다.
- ops console은 이미 `SubmitRun` 후 handle drain이 아니라 event bus router를 쓰는 경계라 이번 변경에서 제외한다.
- Control Plane/Client terminal UI는 제외한다.

### 빌드 등급

- `local-G06`: Edge 내부 API와 두 input surface를 함께 바꾸지만 범위가 세 패키지로 제한되고 테스트가 deterministic하다.

## 구현 체크리스트

- [ ] OpenAI/A2A용 run service interface와 collector가 `RunDispatch`/`RunStream`을 직접 소비하도록 바꾸고 `edge-run-surface` 검증 명령을 통과시킨다.
- [ ] 기존 `Service.SubmitRun` 호환성은 유지하되 신규 DTO 소비를 보장하는 service/openai/a2a 테스트를 갱신한다.
- [ ] 사용자 실행 파이프라인 영향에 대해 보조 smoke/full-cycle 수행 여부 또는 생략 사유를 검증 결과에 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [API-1] Run Surface-Neutral DTO Consumption

문제: `RunDispatch`와 `RunStream`은 이미 정의되어 있지만 `RunHandle`이 신규 surface의 interface까지 점유한다.

Before:

```go
// apps/edge/internal/openai/server.go:17
type runService interface {
	SubmitRun(context.Context, edgeservice.SubmitRunRequest) (*edgeservice.RunHandle, error)
	OllamaAPI(context.Context, edgeservice.OllamaAPIRequest) (edgeservice.OllamaAPIView, error)
}
```

```go
// apps/edge/internal/input/a2a/server.go:19
type runService interface {
	SubmitRun(context.Context, edgeservice.SubmitRunRequest) (*edgeservice.RunHandle, error)
	CancelRun(context.Context, edgeservice.CancelRunRequest) (edgeservice.CommandResult, error)
}
```

해결 방법: service package에 `RunResult` 또는 동등 wrapper를 추가해 `RunDispatch`, `RunStream`, `Close`, `WaitTimeout`을 surface-neutral contract로 노출한다. `Service.SubmitRun`은 호환을 위해 `*RunHandle`을 반환해도 되지만, OpenAI/A2A local interface와 collector 함수는 `RunDispatch`/`RunStream` 중심 타입을 받게 한다. `RunHandle`은 legacy adapter로만 남긴다.

수정 파일 및 체크리스트:

- [ ] `apps/edge/internal/service/run_dispatch.go`: DTO 중심 helper/type 추가, `RunHandle` legacy 주석 갱신.
- [ ] `apps/edge/internal/openai/server.go`: `runService` interface 반환 타입을 DTO wrapper로 축소.
- [ ] `apps/edge/internal/openai/chat_handler.go`: dispatch metadata와 stream을 분리해 response model/run id 참조.
- [ ] `apps/edge/internal/openai/run_result.go`: `RunStream`과 timeout 값을 받도록 collector signature 변경.
- [ ] `apps/edge/internal/openai/stream.go`: stream 함수 signature에서 `RunHandle` 제거.
- [ ] `apps/edge/internal/input/a2a/server.go`: task id 생성과 drain 호출에서 dispatch/stream wrapper 사용.
- [ ] `apps/edge/internal/input/a2a/task_store.go`: `collectBlocking`, `collectBackground`, `drain`에서 `RunHandle` 직접 의존 제거.
- [ ] 관련 tests fake service 반환 타입 갱신.

테스트 작성: 기존 OpenAI/A2A tests를 갱신하고, fake service가 `RunHandle`이 아니라 DTO wrapper를 반환하도록 바꾼다. 별도 신규 test 파일은 만들지 않는다.

중간 검증:

```bash
go test -count=1 ./apps/edge/internal/service ./apps/edge/internal/openai ./apps/edge/internal/input/a2a
```

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/edge/internal/service/run_dispatch.go` | API-1 |
| `apps/edge/internal/openai/server.go` | API-1 |
| `apps/edge/internal/openai/chat_handler.go` | API-1 |
| `apps/edge/internal/openai/run_result.go` | API-1 |
| `apps/edge/internal/openai/stream.go` | API-1 |
| `apps/edge/internal/openai/server_test.go` | API-1 |
| `apps/edge/internal/input/a2a/server.go` | API-1 |
| `apps/edge/internal/input/a2a/task_store.go` | API-1 |
| `apps/edge/internal/input/a2a/server_test.go` | API-1 |
| `apps/edge/internal/input/a2a/task_store_test.go` | API-1 |

## 최종 검증

```bash
go test -count=1 ./apps/edge/internal/service ./apps/edge/internal/openai ./apps/edge/internal/input/a2a
```

예상 결과: 세 패키지 모두 `ok`.

사용자 실행 파이프라인 영향 기록: 보조 smoke/full-cycle 실제 구동을 수행했으면 명령과 출력을 붙이고, 수행하지 못했으면 `testing-smoke` 기준으로 사유와 남은 위험을 `CODE_REVIEW`에 기록한다.

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