<!-- task=m-stream-evidence-gate-core/20+17,19_edge_vertical_slice plan=2 tag=REVIEW_EDGE_VERTICAL_SLICE_CLOSURE -->

# Plan - OpenAI Stream Gate vertical-slice closure

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

구현·테스트·evidence만 수행하고 active pair를 유지한다. 모든 검증을 실제로 실행해 stdout/stderr를 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션에 기록한 뒤 ready for review로 보고한다. 막히면 정확한 차단 원인, 시도한 명령/출력, 재개 조건만 구현 evidence에 남긴다. 사용자 질문·user-input 도구·control-plane stop 파일·next-state 분류·로그 archive·`complete.log` 작성은 하지 않는다. 최종화는 code-review skill 소유다.

## 배경

직전 구현은 lifecycle/terminal outcome과 strategy cap/config는 닫았지만 provider-pool과 tool-validation recovery가 여전히 legacy 경로를 사용한다. 이 follow-up은 기존 OpenAI response shape를 보존하면서 Core가 initial attempt부터 recovery와 release까지 단독 소유하도록 연결하고, SDD S16의 실제 one-cycle evidence와 필수 local full-cycle을 완성한다.

## Archive Evidence Snapshot

- 직전 loop: `agent-task/m-stream-evidence-gate-core/20+17,19_edge_vertical_slice/plan_cloud_G10_1.log`, `agent-task/m-stream-evidence-gate-core/20+17,19_edge_vertical_slice/code_review_cloud_G10_1.log`.
- 판정/요약: FAIL, Required 3 / Suggested 0 / Nit 0. provider-pool actual path switch, enabled tool-validation Core single owner, S16/full-cycle evidence가 미충족이다.
- 완료 유지: current-binding/resource exactly-once cleanup, terminal outcome metric, strategy별 recovery cap, `enabled` load/refresh 계약은 package/race tests에서 통과했다.
- 영향 파일: `apps/edge/internal/openai/{normalized_sse.go,buffered_sse.go,chat_completion.go,chat_handler.go,responses_handler.go,provider_tunnel.go,stream_gate_runtime.go,stream_gate_release_sink.go,tool_validation.go,stream_gate_vertical_slice_test.go,provider_tool_validation_test.go,provider_test_support_test.go}`, `scripts/e2e-openai-vllm.sh`.
- 검증 evidence: 리뷰 시 focused/package/race tests와 auxiliary mock smoke는 PASS했다. 후속 최종화 preflight에서는 active sibling `21+14,19_filter_observation_contract`의 untracked `filter_observation.go`/tests가 epoch/commit-state fixture 4건을 실패시켰지만 이번 target focused race tests는 PASS했다. `IOP_VLLM_MODE=fake ./scripts/e2e-openai-vllm.sh`는 fake provider가 non-stream passthrough를 잘못 거부해 HTTP 400(exit 22)이었고, 필수 `scripts/dev/edge.sh` + `scripts/dev/node.sh` console full-cycle evidence는 없었다.
- Roadmap carryover: `vertical-slice`는 미완료이며 roadmap은 변경하지 않는다. 상세 finding 문맥이 필요할 때만 위 review log를 좁게 읽는다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/stream-evidence-gate-core.md`
- Milestone link: [Milestone 문서](agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/stream-evidence-gate-core.md)
- Task ids:
  - `vertical-slice`: request snapshot부터 single re-admission과 release까지의 production host 한 사이클
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- 규칙/검증: `agent-ops/rules/project/domain/{edge,platform-common,testing}/rules.md`, `agent-test/local/rules.md`, `agent-test/local/{edge-smoke,platform-common-smoke,testing-smoke}.md`, `agent-ops/skills/project/e2e-smoke/SKILL.md`.
- Roadmap/SDD: `agent-roadmap/phase/knowledge-tool-optimization-extension/PHASE.md`, `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/stream-evidence-gate-core.md`, `agent-roadmap/sdd/knowledge-tool-optimization-extension/stream-evidence-gate-core/SDD.md`.
- 계약/spec: `agent-contract/outer/openai-compatible-api.md`, `agent-contract/inner/edge-config-runtime-refresh.md`, `agent-contract/inner/edge-node-runtime-wire.md`, `agent-spec/input/openai-compatible-surface.md`, `agent-spec/runtime/edge-node-execution.md`, `agent-spec/runtime/provider-pool-config-refresh.md`.
- Edge source: `apps/edge/internal/openai/{normalized_sse.go,buffered_sse.go,chat_completion.go,chat_handler.go,responses_handler.go,provider_tunnel.go,stream_gate_runtime.go,stream_gate_dispatcher.go,stream_gate_release_sink.go,tool_validation.go}`.
- Edge tests: `apps/edge/internal/openai/{stream_gate_vertical_slice_test.go,stream_gate_dispatcher_test.go,provider_tool_validation_test.go,provider_test_support_test.go}`.
- 실행 검증: `scripts/e2e-openai-vllm.sh`, `scripts/dev/edge.sh`, `scripts/dev/node.sh`.

### SDD 기준

- SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/stream-evidence-gate-core/SDD.md`, 상태 `[승인됨]`, 잠금 해제.
- 대상: S16 → Milestone `vertical-slice`; Evidence Map은 staged-start `NoopFilter`/`InjectedViolationFilter`, provider/path switch와 backpressure를 포함한 request-to-recovery-to-release one-cycle을 요구한다.
- 연계: S06은 semantic policy를 Edge filter가 소유하고 mutation/retry/rebuild/dispatch는 공통 Coordinator만 소유하도록 요구한다. S12/S14는 all-complete single-flight와 simultaneous intent당 단일 plan/dispatch를, S15는 `SubmitProviderPool` 재입장과 새 actual `AttemptBinding`을, S23은 preparer one-shot lifecycle을 고정한다.
- 따라서 구현 항목은 pool-aware attempt codec/sink, 기존 tool-validation 의미를 재사용하는 terminal-gate consumer, one-cycle host evidence와 실제 구동 검증으로 제한한다.

### 테스트 환경 규칙

- `test_env=local`; `agent-test/local/rules.md`와 edge/platform-common/testing profile을 읽었다. fresh Go test는 `-count=1`, concurrency 경계는 `-race`, OpenAI/provider smoke는 보조 evidence, `scripts/dev/edge.sh` + `scripts/dev/node.sh` console cycle은 필수 full-cycle로 구분한다.
- 테스트 환경 프리플라이트: repo root `/config/workspace/iop-s0`, branch `stream-evidence-gate-core`, HEAD `071cd242`, milestone 작업이 있는 dirty checkout이다. host Go는 `/config/.local/bin/go` → `/config/opt/go/bin/go`, Go `1.26.2`, GOROOT `/config/opt/go`, protoc `/config/.local/bin/protoc`이다. `claude`, `agy`, `codex`, `opencode` command가 모두 존재하며 tracked config는 Edge `:9090`, OpenAI `:8081`, Node `localhost:9090`, console target `codex`를 사용한다. 구현은 이 checkout source에서 dev scripts가 직접 build/run한 binary를 사용하며 별도 stale artifact를 사용하지 않는다.
- 현재 baseline: `go test -count=1 ./packages/go/streamgate`는 active sibling `agent-task/m-stream-evidence-gate-core/21+14,19_filter_observation_contract/` 소유의 `TestFilterObservation_KindSpecificRequiredFields` 3 subtest와 `TestFilterObservation_EpochIDZeroMeansUnbound`에서 실패한다. 이 plan은 해당 sibling source/test를 수정하지 않는다. 최종 package 검증 전에 같은 focused command를 다시 실행해 sibling이 PASS 상태인지 확인하고, 계속 실패하면 exact output과 sibling 완료/수정이라는 재개 조건을 기록한다.
- `scripts/e2e-openai-vllm.sh`는 loopback 임시 포트/config와 fake provider를 사용해 외부 endpoint/secret 없이 실행한다. fake provider가 `stream=false`를 400으로 거부하는 현재 baseline을 script 내부 non-stream JSON fixture 지원으로 고친 뒤 streaming SSE fixture와 함께 검증한다.
- `<확인 필요>` 값은 없다. Go cache는 허용하지 않는다. 실제 CLI 로그인/provider 상태가 실패하면 profile별 명령 출력과 재개 조건을 남기며 auxiliary smoke로 대체하지 않는다.

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

- 기존 coverage 있음: disabled/enabled direct Noop, fixed normalized retry/exhaustion, fixed tunnel pass, current binding cleanup, terminal metric, strategy 0/1 cap, dispatcher의 독립 pool run/tunnel admission.
- 공백: 같은 `RequestRuntime`의 initial pool admission→recovery `SubmitProviderPool`→normalized/tunnel 전환, attempt별 framing 선택, enabled buffered/non-stream tool-validation single owner와 structured `tool_calls`, 두 violation의 단일 dispatch, host preparer success/failure, evaluation 중 producer backpressure, 필수 dev console full-cycle.
- `e2e-openai-vllm.sh`는 provider tunnel의 non-stream과 stream 표면을 함께 주장하지만 fake server는 stream만 허용해 현재 assertion과 fixture가 모순이다.

### 심볼 참조

- rename/remove 예정 없음.
- `retrySubmit` production 호출은 `buffered_sse.go:42`, `chat_completion.go:64` 두 곳이며 enabled 분기에서는 호출되지 않고 disabled compatibility에만 남아야 한다.
- `openAIStreamGateRegistrySnapshot`, `buildOpenAIChatStreamGateRuntime`, `buildOpenAITunnelStreamGateRuntime`, 두 runner와 두 release sink 생성자 호출부를 `apps/edge/internal/openai` 전체에서 확인했다. signature 변경 시 handler와 `stream_gate_vertical_slice_test.go`의 모든 호출부를 함께 갱신한다.
- 새 외부 package는 추가하지 않으며 기존 Go module/표준 라이브러리만 사용한다.

### 분할 판단

- split 정책을 라우팅 전에 재평가했다. 현재 `20+17,19_edge_vertical_slice`는 완료된 archived predecessor `17`과 `19`를 소비하는 기존 integration subtask다.
- pool path switch와 tool-validation은 별도 semantic 영역이지만 둘 다 같은 request-local dispatcher/rebuilder, attempt codec selector, release sink와 S16 one-cycle fixture를 공유한다. 다시 sibling으로 분리하면 한쪽이 임시 이중 retry owner 또는 고정 framing을 남기고 S16 evidence를 독립 PASS할 수 없다. 검증 script 수정도 이 구현의 완료 evidence 복구에만 묶이므로 기존 subtask의 단일 follow-up이 가장 작은 review unit이다.

### 범위 결정 근거

- Core가 semantic detector 의미를 결정하지 않는다. Edge의 기존 `validateToolCallResponse`와 `collectChatCompletionOutput` 의미만 request-local filter/source에 감싸며 repeat/missing-tool/schema-repair 같은 새 consumer 정책은 제외한다.
- provider 선택 알고리즘, service queue 정책, Node wire/proto, observation persistence, managed continuation, agent-family consumer, roadmap 상태는 변경하지 않는다.
- active sibling `21+14,19_filter_observation_contract`의 `packages/go/streamgate/filter_observation*.go` 실패는 이 task에서 고치지 않는다. target 구현이 그 파일과 겹치지 않도록 유지하고 final package PASS만 completion gate로 소비한다.
- `packages/go/streamgate` public interface는 변경하지 않는다. attempt path는 event-source factory가 request-local codec selector를 갱신하고, 복합 sink가 첫 commit 전에 해당 selector를 고정해 기존 normalized SSE/JSON 또는 raw tunnel framing으로 delegate한다.
- `agent-ops/rules/common/**`, `agent-ops/skills/common/**`, 선행 archive는 수정하지 않는다.

### 최종 라우팅

- `evaluation_mode=isolated-reassessment`, `finalizer=finalize-task-policy.sh`, `finalizer_mode=pair`; 이전 loop의 lane/G/score는 입력에서 제외했다.
- build closures: `scope_closed=true`(Required 3과 영향 경로 확정), `context_closed=true`(handler/codec/sink/filter/test/script를 한 pair에서 유지), `verification_closed=true`(target focused PASS와 sibling baseline failure를 구분하는 명령 및 final gate 확정), `evidence_trusted=true`(actual host fixture와 dev entrypoint를 실행), `ownership_closed=true`(Edge semantic/filter/framing과 active observation sibling의 비중첩 소유권 확정), `decision_closed=true`(Milestone 결정 필요 없음, 기존 계약으로 설계 선택 가능).
- build scores: scope_coupling=2, state_concurrency=2, blast_irreversibility=2, evidence_diagnosis=2, verification_complexity=2. closure 전부 true이고 합계 10이므로 `route_basis=grade-boundary`, capability gap=none, lane cloud, grade G10, `PLAN-cloud-G10.md`.
- build loop-risk: ordered transitions state_count=10(response stage/hold/evaluate/arbitrate/abort/rebuild/re-admit/rebind/release/terminal), adverse paths=`simultaneous violation, path switch, preparer failure, cancel, backpressure, exhaustion`; actor_count=4(host/runtime/coordinator/provider), constraints=`single-flight, abort-before-dispatch, exactly-once commit/close`; component_count=5(handler/service/Core/event codec/sink), consumer_count=3(normalized/tunnel/tool-validation), constraints=`immutable snapshot, pure passthrough, structured tool_calls compatibility`; structured mechanisms=`SSE assembly, tool-call synthesis, pool multi-source admission`, hazards=`chunk boundary, nesting, framing selection, stale attempt state`; independent variant axes=4(enabled×path×response mode×outcome), combination verification required=true. matched signatures=`temporal_state,concurrent_consistency,boundary_contract,structured_interpretation,variant_product`, triggered=true, unknown 없음.
- review closures도 모두 true이며 동일한 현재 source/test/contract와 실제 출력으로 판정 가능하다. scores=2/2/2/2/2, `route_basis=official-review`, capability gap=none, lane cloud, grade G10, `CODE_REVIEW-cloud-G10.md`, target Codex `gpt-5.6-sol` xhigh. none floor values: build/review 모두 none.

## 구현 체크리스트

- [ ] REVIEW_EDGE_VERTICAL_SLICE_CLOSURE-1 provider-pool initial/recovery를 Core 한 사이클에 연결하고 actual normalized↔tunnel binding에 맞춰 commit 전 framing을 고정한다.
- [ ] REVIEW_EDGE_VERTICAL_SLICE_CLOSURE-2 enabled buffered/non-stream tool-validation의 hold/validate/rebuild/re-admission을 Core single owner로 이관하면서 structured `tool_calls` 출력을 보존한다.
- [ ] REVIEW_EDGE_VERTICAL_SLICE_CLOSURE-3 S16 simultaneous violation/path-switch/preparer/backpressure fixture와 OpenAI auxiliary smoke 및 필수 dev console full-cycle evidence를 완성한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

## 구현 항목

### [REVIEW_EDGE_VERTICAL_SLICE_CLOSURE-1] provider-pool Core cycle과 attempt별 framing

- 문제: `normalized_sse.go:42`가 provider-pool을 Core에서 제외하고 `chat_handler.go:281-330`, `responses_handler.go:393-446`이 initial `SubmitProviderPool` 결과를 legacy relay한다. `stream_gate_runtime.go:478-523`의 recovery builder/event-source도 fixed run만 허용해 dispatcher에 이미 있는 `openAIAdmissionPool`을 실제 `RequestRuntime`이 소비하지 않는다.
- 해결 방법:

```go
// Before: normalized_sse.go:42
if s.streamGateEnabled() && !dc.route.ProviderPool {
    s.runOpenAIChatStreamGate(w, flusher, dc, handle)
}
// After: handler가 initial pool result 전체를 pool-aware runtime에 넘긴다.
if s.streamGateEnabled() {
    s.runOpenAIChatStreamGate(w, flusher, dc, initialBinding)
}
```

  initial pool request 생성 로직과 recovery admission builder를 공유해 recovery마다 `SubmitProviderPool`을 다시 호출한다. dual event-source factory는 actual transport가 run이면 `openAIRunEventSource`, tunnel이면 `openAITunnelEventSource`를 만들고 request-local codec selector를 commit 전에 갱신한다. 복합 sink는 selector를 첫 `CommitResponseStart`에서 고정하고 normalized Chat SSE/JSON 또는 raw tunnel sink로만 delegate한다. commit 뒤 path 변경은 허용하지 않으며 Core eligibility가 이를 terminal로 수렴시킨다. Chat streaming은 실제 normalized→tunnel 또는 tunnel→normalized recovery 성공을 검증하고, Responses streaming tunnel은 pool recovery admission을 사용하되 normalized candidate의 기존 validation error를 안전한 terminal로 보존한다.
- 수정 파일 및 체크리스트:
  - [ ] `apps/edge/internal/openai/chat_handler.go`, `responses_handler.go`, `normalized_sse.go`, `provider_tunnel.go`: enabled pool initial result를 Core runner에 전달하고 disabled one-shot relay를 유지한다.
  - [ ] `apps/edge/internal/openai/stream_gate_runtime.go`, `stream_gate_release_sink.go`: pool recovery builder, dual source factory, commit-before-freeze codec selector와 composite sink를 구현한다.
  - [ ] `apps/edge/internal/openai/stream_gate_dispatcher.go`: 필요할 때만 pool result의 actual path/dispatch/controller 전달을 보강하되 admission/selection 의미는 바꾸지 않는다.
  - [ ] `apps/edge/internal/openai/stream_gate_vertical_slice_test.go`, `provider_test_support_test.go`: 같은 runtime의 initial/recovery 호출 순서, actual binding, header/role/body/terminal 1회와 discarded attempt 무누출을 검증한다.
- 테스트 작성: `TestStreamGateProviderPoolRecoverySwitchesActualPathOnce`와 Responses pool recovery terminal case를 추가한다. 서로 독립된 dispatcher 호출은 대체 evidence로 인정하지 않는다.
- 중간 검증: `go test -count=1 -run 'TestStreamGateProviderPool|TestOpenAIAttemptDispatcherPool' ./apps/edge/internal/openai` → 같은 Core cycle path-switch fixture와 dispatcher regression PASS.

### [REVIEW_EDGE_VERTICAL_SLICE_CLOSURE-2] enabled tool-validation Core single owner

- 문제: `normalized_sse.go:30-35`가 tool/strict stream을 Core 전에 buffered path로 보내고 `buffered_sse.go:32-64`, `chat_completion.go:54-86`가 enabled 여부와 무관하게 `retrySubmit`을 직접 호출한다. 단순 live text sink로 바꾸면 `collectChatCompletionOutput`이 만드는 구조화된 `tool_calls`와 non-stream JSON shape가 사라진다.
- 해결 방법:

```go
// Before: buffered_sse.go:32-42
if attempt < maxToolValidationAttempts {
    next, submitErr := dc.retrySubmit(r.Context(), retryReq)
}
// After
if !s.streamGateEnabled() {
    return s.streamBufferedChatCompletionLegacy(...)
}
return s.runOpenAIBufferedChatStreamGate(...)
```

  기존 `collectChatCompletionOutput`을 attempt-local buffered event source에서 한 번만 실행하고 결과 holder를 갱신한다. Edge production `toolValidationFilter`는 terminal-gate에서 같은 holder의 `toolValidationErr`/`validateToolCallResponse` 결과를 판단해 exact-replay intent만 반환하며, request mutation/rebuild/dispatch/counter는 Core Coordinator가 소유한다. buffered stream/JSON sink는 pass terminal에서 holder의 기존 `writeBufferedStreamOutput` 또는 `chatCompletionResponse` renderer를 호출해 structured `tool_calls`, content cleanup, finish reason과 usage를 보존한다. pool tunnel actual binding에서는 filter applicability를 path 기준으로 다시 resolve하고 raw passthrough sink를 사용한다. disabled branch는 기존 retry loop와 오류 envelope를 그대로 유지한다.
- 수정 파일 및 체크리스트:
  - [ ] `apps/edge/internal/openai/tool_validation.go`, `stream_gate_runtime.go`: request-local result holder, terminal-gate filter/intent와 buffered event source를 구현한다.
  - [ ] `apps/edge/internal/openai/stream_gate_release_sink.go`: buffered SSE/JSON terminal renderer와 pool composite delegation을 구현한다.
  - [ ] `apps/edge/internal/openai/normalized_sse.go`, `buffered_sse.go`, `chat_completion.go`, `chat_handler.go`: enabled는 Core runner, disabled는 legacy `retrySubmit`으로 분기한다.
  - [ ] `apps/edge/internal/openai/provider_tool_validation_test.go`, `stream_gate_vertical_slice_test.go`: enabled direct/pool, disabled compatibility, valid structured output, malformed/schema failure, exhaustion과 두 filter 동시 violation을 검증한다.
- 테스트 작성: `TestStreamGateEnabledToolValidationHasSingleRecoveryOwner`, `TestStreamGateEnabledToolValidationPreservesStructuredOutput`, disabled compatibility case를 추가한다. enabled assertion은 initial+recovery `SubmitProviderPool` 또는 `SubmitRun` 총 횟수, Core dispatch 1회, legacy callback 0회를 함께 확인한다.
- 중간 검증: `go test -count=1 -run 'TestStreamGate.*ToolValidation|TestChatCompletions.*ToolValidation' ./apps/edge/internal/openai` → enabled single owner와 legacy compatibility PASS.

### [REVIEW_EDGE_VERTICAL_SLICE_CLOSURE-3] S16 및 실행 evidence closure

- 문제: `stream_gate_vertical_slice_test.go:756-759`가 fixed-run/fixed-tunnel 테스트는 S16 path switch 증거가 아니라고 명시한다. simultaneous violation, preparer success/failure, evaluation backpressure도 없고, `scripts/e2e-openai-vllm.sh:122-130`의 fake provider가 script가 요구하는 non-stream request를 스스로 400으로 거부한다. 필수 dev console full-cycle evidence도 없다.
- 해결 방법: 한 host fixture에서 initial staging→두 filter all-complete→plan/abort→optional preparer→rebuilder→`SubmitProviderPool`→actual path rebind→single release/terminal을 order/counter로 검증한다. blocking evaluator 동안 unbuffered producer의 다음 send가 멈추고 해제 뒤 진행되는지 race test로 확인한다. fake vLLM은 `stream=false`일 때 표준 JSON completion, `stream=true`일 때 기존 SSE를 반환하게 해 script의 두 요청 주장과 fixture를 맞춘다. e2e-smoke skill의 `full-cycle` 절차로 dev scripts를 별도 프로세스에서 실행해 register, 동일 session message 2회, node/edge payload 순서 일치와 command 응답을 기록한다.
- 수정 파일 및 체크리스트:
  - [ ] `apps/edge/internal/openai/stream_gate_vertical_slice_test.go`: simultaneous violation single dispatch, preparer success/failure/no-dispatch, producer backpressure, exhaustion/path-switch named fixtures를 추가한다.
  - [ ] `apps/edge/internal/openai/provider_test_support_test.go`: ordered pool results, close/cancel/preparer/backpressure counters를 thread-safe하게 기록한다.
  - [ ] `scripts/e2e-openai-vllm.sh`: fake provider의 non-stream JSON과 stream SSE response를 분리하고 두 output assertion을 유지한다.
  - [ ] `CODE_REVIEW-*-G??.md`: auxiliary mock/OpenAI smoke와 필수 dev full-cycle을 별도 evidence로 기록하고 실패 시 profile/command/output/resume condition을 남긴다.
- 테스트 작성: 위 S16 scenario를 named subtest로 추가하고 script는 `bash -n` 및 fake mode 전체 실행으로 검증한다. 별도 test-only Core 재구현은 만들지 않는다.
- 중간 검증: `go test -race -count=1 -run 'TestStreamGate.*(ProviderPool|Simultaneous|Preparer|Backpressure)' -v ./apps/edge/internal/openai` → 모든 S16 host fixture PASS. `bash -n scripts/e2e-openai-vllm.sh && IOP_VLLM_MODE=fake ./scripts/e2e-openai-vllm.sh` → non-stream+stream auxiliary smoke PASS.

## 의존 관계 및 구현 순서

archived predecessor `17`, `19` PASS → 1(pool binding/framing) → 2(tool-validation consumer/renderer) → 3(combined S16 and execution evidence). subtask directory의 dependency `20+17,19` 외 새 runtime dependency는 없다.

## 수정 파일 요약

| 파일/영역 | 항목 |
|---|---|
| `apps/edge/internal/openai/{chat_handler.go,responses_handler.go,normalized_sse.go,provider_tunnel.go,stream_gate_runtime.go,stream_gate_dispatcher.go,stream_gate_release_sink.go}` | 1, 2 |
| `apps/edge/internal/openai/{buffered_sse.go,chat_completion.go,tool_validation.go,provider_tool_validation_test.go}` | 2 |
| `apps/edge/internal/openai/{stream_gate_vertical_slice_test.go,provider_test_support_test.go}` | 1, 2, 3 |
| `scripts/e2e-openai-vllm.sh` | 3 |

## 최종 검증

- `command -v go && readlink -f "$(command -v go)" && go version && go env GOROOT && command -v protoc && command -v claude && command -v agy && command -v codex && command -v opencode` → host tool identity와 full-cycle command preflight 기록.
- `git rev-parse --show-toplevel && git branch --show-current && git rev-parse --short HEAD && git status --short` → 현재 checkout/source state 기록.
- `go test -count=1 -run 'TestFilterObservation_KindSpecificRequiredFields|TestFilterObservation_EpochIDZeroMeansUnbound' ./packages/go/streamgate` → active sibling baseline PASS가 final package gate 전에 복구됐는지 확인한다. 실패 시 이 task 범위를 확장하지 않고 exact blocker/resume condition을 기록한다.
- `make proto` → generated proto consistency PASS, 예상 밖 generated diff 없음.
- `go test -count=1 ./packages/go/config ./packages/go/streamgate ./apps/edge/internal/openai ./apps/edge/internal/service ./apps/edge/internal/configrefresh` → fresh package PASS.
- `go test -race -count=1 ./packages/go/streamgate ./apps/edge/internal/openai ./apps/edge/internal/service` → race PASS.
- `go test -race -count=1 -run 'TestStreamGate.*(ProviderPool|ToolValidation|Simultaneous|Preparer|Backpressure)' -v ./apps/edge/internal/openai` → 새 S16/tool-validation named fixtures 전부 PASS; 0 tests는 실패.
- `bash -n scripts/e2e-openai-vllm.sh && IOP_VLLM_MODE=fake ./scripts/e2e-openai-vllm.sh` → auxiliary OpenAI non-stream+stream provider smoke PASS.
- `IOP_E2E_BIND_TIMEOUT=60 IOP_E2E_PROFILE=mock ./scripts/e2e-smoke.sh` → auxiliary mock smoke PASS로 별도 기록.
- terminal A에서 `./scripts/dev/edge.sh`, terminal B에서 `./scripts/dev/node.sh`를 실행하고 Edge console에 `/nodes`, `Convert token iop_manual_one and reply only with converted token`, `Convert token iop_manual_two and reply only with converted token`, `/capabilities`, `/transport`, `/sessions`, `/terminate-session`을 순서대로 입력한다 → registered, 각 run의 sent/start/non-empty message/complete, node local payload와 edge rendered payload의 내용·순서 일치, complete-after-last-message, command 결과 PASS. auxiliary smoke로 대체하지 않는다.
- `rg -n --sort path 'retrySubmit\(' apps/edge/internal/openai/buffered_sse.go apps/edge/internal/openai/chat_completion.go` → 남은 두 legacy 호출이 명시적 runtime-disabled compatibility branch 안에만 존재한다.
- `git diff --check` → no output, exit 0.

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다.
