<!-- task=m-openai-compatible-raw-tunnel-sideband-passthrough/02+01_edge_stream plan=0 tag=API -->

# Code Review Reference - API

> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> The task is NOT complete until every implementation-owned section below is filled in.
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
> If implementation is blocked by a selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with linked evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`.
> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation.
> Finalization is review-agent-only.

## 개요

date=2026-07-07
task=m-openai-compatible-raw-tunnel-sideband-passthrough/02+01_edge_stream, plan=0, tag=API

## Roadmap Targets

- Milestone: `agent-roadmap/phase/routing-policy-model-orchestration/milestones/openai-compatible-raw-tunnel-sideband-passthrough.md`
- Milestone link: [Milestone 문서](agent-roadmap/phase/routing-policy-model-orchestration/milestones/openai-compatible-raw-tunnel-sideband-passthrough.md)
- Task ids:
  - `edge-stream`: Edge OpenAI-compatible handler는 tunnel status/headers/body chunks를 caller에게 그대로 write하고 pure passthrough를 기본값으로 적용한다.
- Completion mode: check-on-pass

## 이 파일을 읽는 리뷰 에이전트에게

> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.

각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [API-1] Tunnel Frame Routing | [x] |
| [API-2] OpenAI Passthrough Writer | [x] |

## 구현 체크리스트

- [x] `01_node_relay` predecessor `complete.log`를 확인하고 경로를 review stub에 기록한다. 경로: `agent-task/archive/2026/07/m-openai-compatible-raw-tunnel-sideband-passthrough/01_node_relay/complete.log` (PASS, `node-relay` Roadmap Completion 포함)
- [x] Edge transport가 `ProviderTunnelFrame`을 request-bound channel로 라우팅하되 `events.Bus`에는 publish하지 않게 구현한다. (`transport.Server.SetTunnelFrameHandler` → `service.RouteProviderTunnelFrame` → tunnel_id별 request-bound channel; negative test: `TestServerRoutesTunnelFramesToTunnelHandlerNotRunHandler`, `TestRouteProviderTunnelFrameDoesNotPublishToBus`)
- [x] Edge service에 provider-pool/direct route 공통 `SubmitProviderTunnel` 경로를 추가하고 admission slot release를 END/ERROR/cancel에서 보장한다. (release 검증: `TestSubmitProviderTunnelProviderPoolSendsRequestAndReleasesSlotOnEnd`, `TestSubmitProviderTunnelErrorFrameReleasesSlot`, `TestSubmitProviderTunnelCloseReleasesSlot`)
- [x] OpenAI Chat Completions handler가 OpenAI-compatible provider route의 omitted mode를 pure `passthrough`로 처리하고 provider status/header/body bytes를 caller에게 그대로 쓴다. 검증: streaming/non-streaming Chat Completions fixture가 provider 원본과 byte-identical하게 반환된다. (`TestChatCompletionsPassthroughNonStreamingByteIdentity`, `TestChatCompletionsPassthroughStreamingByteIdentity`, `TestChatCompletionsPassthroughProviderErrorStatusRelayed`)
- [x] caller disconnect/context cancel이 Node cancel/control path로 전파되는지 test한다. (`TestChatCompletionsPassthroughContextCancelSendsCancelRun`: 취소된 request context에서 `CancelRun(run-tunnel, node-1)` 호출 확인)
- [x] `go test ./apps/edge/internal/transport ./apps/edge/internal/service ./apps/edge/internal/openai`를 실행한다. (PASS; `-count=1 -race`도 PASS)
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 코드리뷰 전용 체크리스트

> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.

- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [x] predecessor `01_node_relay` completion evidence가 유효하다.
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G07_N.log`로 아카이브한다.
- [x] active `PLAN-*-G??.md`를 `plan_cloud_G07_M.log`로 아카이브한다.
- [ ] PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-openai-compatible-raw-tunnel-sideband-passthrough/02+01_edge_stream/`로 이동한다.
- [x] FAIL이고 user-review gate가 트리거되지 않았으므로 다음 active `PLAN-local-G04.md`와 `CODE_REVIEW-local-G04.md`를 작성하고 `complete.log`를 작성하지 않는다.
- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다.

## 계획 대비 변경 사항

- tunnel frame router(`providerTunnelRouter`)를 transport가 아니라 `apps/edge/internal/service/run_dispatch.go`에 두었다. 계획은 "transport/server 또는 service"를 허용했고, request-bound 구독 상태는 dispatch lifecycle과 함께 service가 소유하는 것이 기존 `SetRunEventHandler` 패턴과 일치한다. transport는 `SetTunnelFrameHandler` 콜백만 노출한다.
- 계획 파일 목록 외 수정 2건: `apps/edge/internal/service/service.go`(Service struct에 `tunnels` 필드와 `New()` 초기화), `apps/edge/internal/bootstrap/runtime.go`(`wireHandlers()`에 `SetTunnelFrameHandler(Service.RouteProviderTunnelFrame)` 배선). router가 실제 런타임에 연결되기 위해 필요했다.
- `apps/edge/internal/openai/run_result.go`는 수정하지 않았다. 기존 `isCancelWorthyRunError`/`errRunTimedOut`을 그대로 재사용했고, cancel 전송 핵심만 `apps/edge/internal/openai/server.go`의 `sendCancelRun`으로 분리해 passthrough writer의 mid-stream write 실패 경로에서 재사용한다.
- transport 테스트는 새 `tunnel_test.go` 대신 기존 `apps/edge/internal/transport/server_test.go`에, service 테스트는 기존 `apps/edge/internal/service/run_dispatch_internal_test.go`에 추가했다 (기존 파일 수정 우선 규칙).
- omitted mode 기본값 변경으로, 정규화 경로 동작을 검증하던 기존 openai 테스트 30개(provider adapter `openai_compat`/`vllm` 또는 provider-pool catalog route)가 tunnel 경로로 넘어가 실패했다. 해당 테스트의 요청 본문에 `"metadata":{"iop_response_mode":"transformed"}`를 추가해 명시적 opt-out으로 정규화 경로 검증을 유지했고, `TestChatCompletionsProviderPoolDispatch`는 tunnel dispatch 검증으로 재작성했다.

## 주요 설계 결정

- mode 해석(잠정): 누락 또는 명시적 `"passthrough"` → tunnel passthrough. 그 외 명시 값(`transformed` 등)은 기존 normalized 경로로 유지한다. unknown mode fail-fast(S11)와 `passthrough+sideband`/`transformed` 정식 semantics는 별도 `mode-contract`/`transform-output` Task 범위라 이 단계에서는 silent fallback을 기존 경로 보존 형태로 남겼다.
- tunnel 대상 route: `dispatch.ProviderPool == true` 또는 legacy route adapter type이 정확히 `openai_compat`/`vllm`인 경우. instance-key로 명명된 adapter route는 이번 범위에서 normalized 경로를 유지한다. provider-pool 후보 adapter가 tunnel 미지원이면 Node가 ERROR frame을 돌려주고 caller는 502를 받는다.
- passthrough 요청 본문은 caller 원본 raw JSON에서 `model` 필드만 admission 후 확정된 served target으로 rewrite한다(`SubmitProviderTunnelRequest.BuildBody` 콜백). catalog generation policy(`DefaultMaxTokens`, `DefaultThinkingTokenBudget`)는 pure passthrough 요청에 주입하지 않는다 — provider-original 원칙상 provider 기본값을 따르며, 정책 주입이 필요한 caller는 명시 모드(현재 normalized 경로)를 쓴다.
- 프레임 전달: tunnel_id별 buffered channel(4096) + 구독자가 살아있는 동안 blocking 전달로 drop/reorder 없음(단일 TCP 연결이라 순서 보존). 구독 해제 이후 도착한 frame은 drop. 세밀한 backpressure 튜닝은 S09 `backpressure-cancel` Task 범위.
- slot release: forward goroutine이 END/ERROR frame을 관찰하거나 `handle.Close()`(cancel 포함)가 호출되면 `sync.Once`로 `queue.releaseRun`을 정확히 1회 호출한다. tunnel은 `events.Bus`를 타지 않으므로 기존 event watcher 대신 이 경로가 release를 소유한다. node disconnect는 기존 `releaseNode` bus watcher가 처리한다.
- 응답 relay: RESPONSE_START의 status/headers를 hop-by-hop 헤더(Connection, Transfer-Encoding 등) 제외 전부 복사 후 즉시 flush(SSE 헤더 선행), BODY frame은 순서대로 write+flush, USAGE frame은 sideband 후보라 pure passthrough에서 무시(SDD D04), RESPONSE_START 이전 ERROR는 502 JSON, 이후 ERROR는 이미 status가 커밋되어 truncate 후 종료.
- caller disconnect는 `r.Context().Done()`과 mid-stream write 실패 두 경로 모두 `CancelRun`(CANCEL_RUN action)으로 Node cancel path에 전파하고, `defer handle.Close()`가 slot release와 구독 해제를 보장한다.

## 사용자 리뷰 요청

_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._

- 상태: 없음
- 사유 유형: 없음
- 연결 대상: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음

## 리뷰어를 위한 체크포인트

- Pure passthrough body에 IOP sideband/event/field가 섞이지 않는가.
- Streaming/non-streaming body byte comparison이 provider fixture와 동일한가.
- Provider-pool admission slot이 END/ERROR/cancel에서 release되는가.

## 검증 결과

### API-1 중간 검증
```bash
$ go test -count=1 ./apps/edge/internal/transport ./apps/edge/internal/service
ok  	iop/apps/edge/internal/transport	2.041s
ok  	iop/apps/edge/internal/service	0.714s
```

### API-2 중간 검증
```bash
$ go test -count=1 ./apps/edge/internal/openai
ok  	iop/apps/edge/internal/openai	1.582s
```

### 최종 검증
```bash
$ go test ./apps/edge/internal/transport ./apps/edge/internal/service ./apps/edge/internal/openai
ok  	iop/apps/edge/internal/transport	(cached)
ok  	iop/apps/edge/internal/service	(cached)
ok  	iop/apps/edge/internal/openai	(cached)

$ go test -count=1 -race ./apps/edge/internal/transport ./apps/edge/internal/service ./apps/edge/internal/openai
ok  	iop/apps/edge/internal/transport	3.062s
ok  	iop/apps/edge/internal/service	1.739s
ok  	iop/apps/edge/internal/openai	2.720s

$ go test -count=1 ./apps/edge/...
ok  	iop/apps/edge/cmd/edge	0.060s
ok  	iop/apps/edge/internal/bootstrap	0.303s
ok  	iop/apps/edge/internal/configrefresh	0.033s
ok  	iop/apps/edge/internal/controlplane	4.457s
ok  	iop/apps/edge/internal/edgecmd	0.014s
ok  	iop/apps/edge/internal/edgevalidate	0.005s
ok  	iop/apps/edge/internal/events	0.007s
ok  	iop/apps/edge/internal/input	0.006s
ok  	iop/apps/edge/internal/input/a2a	0.005s
ok  	iop/apps/edge/internal/node	0.005s
ok  	iop/apps/edge/internal/openai	1.580s
ok  	iop/apps/edge/internal/opsconsole	0.006s
ok  	iop/apps/edge/internal/service	0.703s
ok  	iop/apps/edge/internal/transport	2.038s

$ gofmt -l apps/edge && git diff --check
(출력 없음 — PASS)
```

## 코드리뷰 결과

- 종합 판정: FAIL
- 차원별 평가:
  - Correctness: Fail
  - Completeness: Fail
  - Test coverage: Fail
  - API contract: Fail
  - Code quality: Pass
  - Implementation deviation: Fail
  - Verification trust: Pass
  - Spec conformance: Fail
- 발견된 문제:
  - Required: `apps/edge/internal/openai/chat_handler.go:65`에서 omitted-mode provider-pool 요청이 곧바로 `tunnelChatCompletionPassthrough`로 빠지면서 `applyModelCatalogGenerationPolicyToChat`가 실행되지 않습니다. 그런데 active 계약은 provider-pool `models[]`의 `default_max_tokens`, `min_max_tokens`, `default_thinking_token_budget`을 OpenAI-compatible 요청을 내부 실행으로 넘기기 전에 적용하도록 요구합니다(`agent-contract/outer/openai-compatible-api.md`, `agent-contract/inner/edge-config-runtime-refresh.md`). 현재 테스트도 `TestChatCompletionsProviderPoolAppliesGenerationPolicy`를 `metadata.iop_response_mode=transformed`로 바꿔 omitted-mode passthrough 회귀를 잡지 못합니다. Fix: passthrough 분기 전에 provider-pool catalog generation policy를 적용하거나, `BuildBody`에서 served target rewrite와 함께 같은 정책이 반영된 request body를 만들고, omitted-mode provider-pool passthrough body가 `max_tokens`/`thinking_token_budget` 정책을 포함한다는 테스트를 추가하세요.
- 다음 단계: FAIL 후속 `PLAN-local-G04.md`와 `CODE_REVIEW-local-G04.md`를 작성했다.
