<!-- task=m-openai-compatible-raw-tunnel-sideband-passthrough/05_backpressure_cancel plan=0 tag=FLOWCTRL -->

# Plan - FLOWCTRL

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

구현 후 `CODE_REVIEW-local-G08.md`의 구현 에이전트 소유 섹션을 반드시 실제 구현 내용과 검증 출력으로 채운다. 검증을 실행하고, active 파일은 그대로 두고, 리뷰 준비 상태를 보고한다. 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단하면 review stub의 `사용자 리뷰 요청` 섹션에 정확한 근거를 적고 멈춘다. 구현 중 사용자에게 직접 묻거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 환경/secret/service blocker, 일반 범위 변경, evidence gap은 사용자 리뷰 요청이 아니라 검증 결과나 후속 plan으로 처리한다.

## 배경

Raw tunnel은 provider body bytes를 기존 Edge-Node socket으로 전달한다. 현재 코드에는 request-bound tunnel router, channel backpressure, HTTP caller cancel 전파, Node adapter context cancellation이 부분적으로 있지만, slow client/write failure/provider error/ordered stream을 S09 evidence로 한 번에 묶는 검증이 부족하다.

## 사용자 리뷰 요청 흐름

사용자 리뷰 요청은 선택된 Milestone lock decision이 구현을 차단할 때만 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. direct chat prompt는 금지이며, code-review가 이 요청을 검증하고 필요한 경우에만 실제 `USER_REVIEW.md`를 작성한다.

## 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:
  - `backpressure-cancel`: tunnel path는 기존 socket 위에서 ordered, lossless, backpressure-aware stream을 유지하고 cancel/control frame을 지연시키지 않는다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-roadmap/current.md`
- `agent-roadmap/phase/routing-policy-model-orchestration/milestones/openai-compatible-raw-tunnel-sideband-passthrough.md`
- `agent-roadmap/sdd/routing-policy-model-orchestration/openai-compatible-raw-tunnel-sideband-passthrough/SDD.md`
- `agent-ops/rules/project/domain/edge/rules.md`
- `agent-ops/rules/project/domain/node/rules.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/edge-smoke.md`
- `agent-test/local/node-smoke.md`
- `apps/edge/internal/openai/stream.go`
- `apps/edge/internal/openai/server_test.go`
- `apps/edge/internal/service/run_dispatch.go`
- `apps/edge/internal/service/model_queue_test.go`
- `apps/node/internal/adapters/openai_compat/openai_compat.go`
- `apps/node/internal/adapters/openai_compat/openai_compat_test.go`
- `apps/node/internal/adapters/vllm/vllm.go`
- `apps/node/internal/adapters/vllm/vllm_test.go`
- `apps/node/internal/node/node.go`
- `apps/node/internal/node/node_test.go`

### SDD 기준

- SDD: `agent-roadmap/sdd/routing-policy-model-orchestration/openai-compatible-raw-tunnel-sideband-passthrough/SDD.md`
- 상태: `[승인됨]`, SDD 잠금 `해제`, 사용자 리뷰 없음.
- Target scenario: S09 -> Milestone Task `backpressure-cancel`.
- Evidence Map row: slow client, cancel, provider error, socket backpressure test. 구현 체크리스트는 Edge HTTP writer, Edge service tunnel router, Node adapter cancellation tests를 모두 요구한다.

### 테스트 환경 규칙

- test_env: local.
- 읽은 규칙: `agent-test/local/rules.md`, `agent-test/local/edge-smoke.md`, `agent-test/local/node-smoke.md`.
- 적용 명령: local Go package tests, race test for Edge service/openai, Node adapter tunnel provider tests.
- 원격/field preflight: 이 plan은 deterministic local tests로 S09를 닫는다. 실제 provider field smoke는 별도 dev-runtime evidence로 보강 가능하지만 PASS 필수 조건은 아니다.

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

- Edge OpenAI: `TestChatCompletionsPassthroughContextCancelSendsCancelRun`은 pre-cancel case만 확인한다. write failure after body start, sideband writer cancel, slow frame consumption ordering이 부족하다.
- Edge service: tunnel router는 full buffer에서 block하도록 설계됐지만 backpressure/blocking release behavior의 focused test가 부족하다.
- Node adapters: `openai_compat`와 `vllm`는 cancellation/body read error tests가 있으나 Edge write failure -> CancelRun -> upstream context cancel end-to-end fixture가 연결되어 있지 않다.

### 심볼 참조

- renamed/removed symbols: none.

### 분할 판단

- split policy 평가 완료.
- 공유 task group: `agent-task/m-openai-compatible-raw-tunnel-sideband-passthrough/`.
- sibling plan:
  - `04_assembled_log`: `assembled-log`, 독립.
  - `05_backpressure_cancel`: 이 plan, `backpressure-cancel` 전용.
- 의존 관계: 없음. 기존 archived `01_node_relay`, `02+01_edge_stream`, `03+02_response_modes_semantics`는 배경 evidence지만 이 subtask의 runtime predecessor는 아니다.

### 범위 결정 근거

- 포함: Edge OpenAI tunnel writers, Edge service tunnel router/release tests, Node provider tunnel cancellation/error tests.
- 제외: 새로운 transport, separate stream server, gRPC/WebSocket 전환. Milestone 범위 제외와 domain rule 금지 사항에 해당한다.
- 제외: raw payload persistence. Flow-control task는 payload storage를 추가하지 않는다.

### 빌드 등급

- `local-G08`: concurrency/backpressure/cancel path가 Edge HTTP, Edge service, Node adapters를 가로지르지만 deterministic local tests와 race test로 검증 가능하다.

## 구현 체크리스트

- [ ] `writeProviderTunnelResponse`, `writeProviderTunnelSidebandStream`, `writeProviderTunnelSidebandResponse`가 caller disconnect, write failure, timeout에서 exactly-once cancel/close를 수행하는지 보강한다.
- [ ] `providerTunnelRouter`와 `openProviderTunnel`의 full-buffer/backpressure와 terminal release 동작을 focused service test로 검증한다.
- [ ] `openai_compat`와 `vllm` tunnel provider cancellation/body error tests가 ordered ERROR/END semantics와 upstream request cancellation을 충분히 검증하도록 보강한다.
- [ ] slow client/write failure/provider error fixture에서 chunk drop/reorder가 없고 cancel/control frame이 지연되지 않는지 검증한다.
- [ ] `go test -count=1 ./apps/edge/internal/openai ./apps/edge/internal/service`를 통과시킨다.
- [ ] `go test -count=1 ./apps/node/internal/adapters/openai_compat ./apps/node/internal/adapters/vllm`를 통과시킨다.
- [ ] `go test -count=1 -race ./apps/edge/internal/openai ./apps/edge/internal/service`를 통과시킨다.
- [ ] `git diff --check`를 통과시킨다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [FLOWCTRL-1] Edge HTTP Tunnel Cancel And Ordering

문제: [stream.go](/config/workspace/iop/apps/edge/internal/openai/stream.go:401)는 context cancel/timeout/write error에서 cancel을 보내지만 write failure와 sideband/non-streaming buffering까지 S09로 묶는 검증이 부족하다.

해결 방법: fake `ResponseWriter` 또는 cancellable request fixture를 추가해 body write failure after response start, sideband writer write failure, timeout, provider ERROR before/after start를 검증한다. Body chunks는 sequence대로 caller에 쓰이고, error/cancel 후 추가 chunk가 쓰이지 않아야 한다.

수정 파일 및 체크리스트:

- [ ] `apps/edge/internal/openai/stream.go`: 필요 시 exactly-once close/cancel helper를 도입하되 response mode semantics는 유지한다.
- [ ] `apps/edge/internal/openai/server_test.go`: write failure, sideband cancel, provider error/order tests 추가.

테스트 작성: 작성. S09 직접 evidence다.

중간 검증:

```bash
go test -count=1 ./apps/edge/internal/openai -run 'TestChatCompletionsPassthrough.*(Cancel|WriteFailure|ProviderError|ByteIdentity)|TestChatCompletionsPassthroughSideband.*(Cancel|WriteFailure)'
```

### [FLOWCTRL-2] Service Tunnel Backpressure

문제: [run_dispatch.go](/config/workspace/iop/apps/edge/internal/service/run_dispatch.go:719)의 `providerTunnelRouter`는 full buffer에서 block하도록 설계됐지만, 이 behavior와 handle close release가 테스트로 고정되어 있지 않다.

해결 방법: service-level test를 추가해 subscriber channel이 가득 찬 동안 route goroutine이 drop 없이 block하고, `Close()` 또는 terminal frame에서 unblock/release하는지 확인한다. Provider-pool tracked tunnel은 END/ERROR/Close 각각에서 slot이 한 번만 release되어야 한다.

수정 파일 및 체크리스트:

- [ ] `apps/edge/internal/service/run_dispatch.go`: 필요 시 testability를 위한 작은 helper만 추가한다.
- [ ] `apps/edge/internal/service/run_dispatch_internal_test.go` 또는 `service_internal_test.go`: backpressure/release tests 추가.

테스트 작성: 작성. Concurrency path이므로 race-friendly deterministic channel test로 만든다.

중간 검증:

```bash
go test -count=1 ./apps/edge/internal/service -run 'TestProviderTunnel.*(Backpressure|Release|Close)'
```

### [FLOWCTRL-3] Node Provider Cancellation

문제: [openai_compat.go](/config/workspace/iop/apps/node/internal/adapters/openai_compat/openai_compat.go:80)와 [vllm.go](/config/workspace/iop/apps/node/internal/adapters/vllm/vllm.go:63)는 context 기반 provider request를 사용하지만 Edge cancel과 upstream request cancel의 S09 evidence가 adapter별로 충분히 묶여 있지 않다.

해결 방법: adapter tests에 server-side cancel observation, ordered sequence assertion, body read error 후 single ERROR terminal assertion을 강화한다. Existing tests를 확장하되 duplicated ERROR frame을 허용하지 않는다.

수정 파일 및 체크리스트:

- [ ] `apps/node/internal/adapters/openai_compat/openai_compat_test.go`: cancellation/body read error ordering tests 보강.
- [ ] `apps/node/internal/adapters/vllm/vllm_test.go`: cancellation/body read error ordering tests 보강.

테스트 작성: 작성. Existing `TunnelProvider_Cancel`과 `TunnelProvider_BodyReadError`를 강화하거나 새 test를 추가한다.

중간 검증:

```bash
go test -count=1 ./apps/node/internal/adapters/openai_compat ./apps/node/internal/adapters/vllm -run 'Test(OpenAICompat|Vllm)TunnelProvider'
```

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/edge/internal/openai/stream.go` | FLOWCTRL-1 |
| `apps/edge/internal/openai/server_test.go` | FLOWCTRL-1 |
| `apps/edge/internal/service/run_dispatch.go` | FLOWCTRL-2 |
| `apps/edge/internal/service/run_dispatch_internal_test.go` | FLOWCTRL-2 |
| `apps/node/internal/adapters/openai_compat/openai_compat_test.go` | FLOWCTRL-3 |
| `apps/node/internal/adapters/vllm/vllm_test.go` | FLOWCTRL-3 |

## 최종 검증

```bash
go test -count=1 ./apps/edge/internal/openai ./apps/edge/internal/service
go test -count=1 ./apps/node/internal/adapters/openai_compat ./apps/node/internal/adapters/vllm
go test -count=1 -race ./apps/edge/internal/openai ./apps/edge/internal/service
git diff --check
```

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