<!-- task=m-seulgivibe-openai-compatible-provider/02+01_provider_tunnel_auth plan=1 tag=REVIEW_SEULGI_TUNNEL -->

# Implementation Plan - REVIEW_SEULGI_TUNNEL

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

`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우는 것이 필수 완료 조건이다. 구현 후 검증을 실행하고, 실제 stdout/stderr를 붙이고, active 파일은 그대로 둔 뒤 review ready로 보고한다. selected Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 막는 경우에만 review stub의 `사용자 리뷰 요청`에 정확한 근거를 기록하고 멈춘다. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 외부 secret/service 준비, 검증 증거 공백, 일반 범위 조정은 사용자 리뷰 요청이 아니라 후속 plan 또는 검증 기록으로 처리한다. finalization은 code-review-skill 전용이다.

## 배경

직전 리뷰는 `FAIL`이다. `02+01_provider_tunnel_auth`의 계획 파일과 리뷰 스텁은 있었지만, `apps/edge/internal/openai` 변경이 없고, provider auth forwarding/missing-required 테스트도 없으며, 구현 에이전트 소유 리뷰 섹션과 검증 출력도 비어 있었다. 이 follow-up은 SDD S03과 Milestone task `provider-token-tunnel`을 만족하도록 Edge OpenAI Chat Completions provider tunnel에 request-time provider auth header forwarding을 구현하고 증거를 복구한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/routing-policy-model-orchestration/milestones/seulgivibe-openai-compatible-provider.md`
- Milestone link: [Milestone 문서](agent-roadmap/phase/routing-policy-model-orchestration/milestones/seulgivibe-openai-compatible-provider.md)
- Task ids:
  - `provider-token-tunnel`: Edge OpenAI Chat Completions provider tunnel이 request-time raw user token을 provider Authorization header로 전달
- Completion mode: check-on-pass

## Archive Evidence Snapshot

- Current archived plan log: `agent-task/m-seulgivibe-openai-compatible-provider/02+01_provider_tunnel_auth/plan_cloud_G07_0.log`
- Current archived review log: `agent-task/m-seulgivibe-openai-compatible-provider/02+01_provider_tunnel_auth/code_review_cloud_G07_0.log`
- Verdict: FAIL
- Review number: 1
- Required summary:
  - Required 1: `apps/edge/internal/openai/stream.go:370` still creates `SubmitProviderTunnelRequest` without `Headers`; provider auth helper/injection/missing-required behavior is not implemented.
  - Required 2: `apps/edge/internal/openai/server_test.go:77` captures tunnel requests, but no provider auth forwarding/missing-required/inbound auth separation tests exist.
  - Required 3: `CODE_REVIEW-cloud-G07.md` implementation-owned sections and verification output were blank, so completion and verification trust were not judgeable.
- Affected files:
  - `apps/edge/internal/openai/stream.go`
  - `apps/edge/internal/openai/server_test.go`
- Verification evidence:
  - `git diff -- apps/edge/internal/openai` was empty at review time.
  - `rg -n "providerTunnelAuthHeaders|errProviderAuthRequired|ProviderAuth|X-IOP-Provider-Authorization|TargetHeader|FromHeader" apps/edge/internal/openai apps/edge/internal/openai/server_test.go` found only unrelated bearer-token helper references and no provider auth implementation/tests.
  - `GOCACHE=/tmp/iop-go-build-cache go test ./apps/edge/internal/openai -run 'TestProviderTunnelAuthHeaders|TestChatProviderTunnelProviderAuth|TestChatProviderTunnelForwardsProviderAuthHeader' -count=1` returned `ok iop/apps/edge/internal/openai 0.008s [no tests to run]`.
  - Plain `go test ./apps/edge/internal/openai -run 'TestProviderTunnelAuthHeaders|TestChatProviderTunnelProviderAuth|TestChatProviderTunnelForwardsProviderAuthHeader' -count=1` failed before package setup with `/config/.cache/go-build/... permission denied`; if this environment issue persists, record the failure and rerun with a temporary `GOCACHE` while keeping the same package/test arguments.
- Roadmap carryover:
  - SDD S03 requires configured provider auth request header raw token to become provider `Authorization`, and missing-required must be blocked before dispatch.
  - `agent-task/archive/2026/07/m-seulgivibe-openai-compatible-provider/01_config_auth_catalog/complete.log` shows predecessor `config-auth-catalog` completed and this split dependency is satisfied.
- Allowed narrow reread:
  - `agent-task/m-seulgivibe-openai-compatible-provider/02+01_provider_tunnel_auth/plan_cloud_G07_0.log`
  - `agent-task/m-seulgivibe-openai-compatible-provider/02+01_provider_tunnel_auth/code_review_cloud_G07_0.log`
  - `agent-task/archive/2026/07/m-seulgivibe-openai-compatible-provider/01_config_auth_catalog/complete.log`

## 분석 결과

### 계약 / SDD 기준

- `agent-contract/outer/openai-compatible-api.md`의 Provider auth forwarding 계약은 `openai.provider_auth.enabled=true`일 때 caller가 `from_header`에 담은 raw user token을 provider tunnel request의 `target_header`로 전달한다고 정의한다.
- 같은 계약과 SDD는 IOP inbound auth `Authorization: Bearer <token>`을 provider credential source로 재사용하지 말라고 금지한다.
- SDD Evidence Map의 S03 완료 증거는 Edge OpenAI handler auth forwarding/missing tests와 `go test ./apps/edge/internal/openai -count=1` 결과다.

### 현재 코드 상태

- `packages/go/config/config.go`에는 01에서 추가된 `EdgeOpenAIProviderAuthConf`와 기본값 normalization이 있다.
- `apps/edge/internal/openai/stream.go`의 `submitChatCompletionTunnel`은 아직 `ProviderAuth` 설정을 읽지 않고 `SubmitProviderTunnelRequest.Headers`도 채우지 않는다.
- `apps/edge/internal/openai/server_test.go`의 fake service는 tunnel request를 저장하므로 `Headers` assertion을 추가할 수 있다.

### 범위 결정 근거

- 이 follow-up은 Chat Completions provider tunnel auth만 다룬다. `/v1/responses` passthrough는 sibling task `03+01,02_responses_passthrough` 범위다.
- Node adapter/proto/service tunnel relay는 이미 `SubmitProviderTunnelRequest.Headers`를 전달할 수 있으므로, 이 task에서 변경하지 않는다.
- 실제 token 값, host-local helper, env secret, Codex/Claude local config를 읽거나 기록하지 않는다.

### 빌드 등급

`cloud-G07`. 외부 OpenAI-compatible auth 계약, raw token forwarding 금지/허용 경계, SDD Evidence Map 검증이 핵심이다.

## 구현 체크리스트

- [ ] [REVIEW_SEULGI_TUNNEL-1] Provider auth helper를 추가하고 Chat Completions provider tunnel 요청에 provider auth headers를 주입한다.
- [ ] [REVIEW_SEULGI_TUNNEL-2] provider auth forwarding, missing-required no-dispatch, inbound IOP auth 분리 handler tests를 추가하고 targeted test를 실행한다.
- [ ] [REVIEW_SEULGI_TUNNEL-3] 최종 `go test ./apps/edge/internal/openai -count=1` 결과와 구현 내용을 CODE_REVIEW-cloud-G07.md 구현 에이전트 소유 섹션에 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_SEULGI_TUNNEL-1] Provider Auth Helper And Injection

#### 문제

`apps/edge/internal/openai/stream.go:370`의 `SubmitProviderTunnelRequest`는 `Headers` 없이 만들어진다. `openai.provider_auth` 설정은 01에서 생겼지만 Edge OpenAI surface가 request-time provider token을 읽어 provider tunnel header로 변환하지 않는다.

#### 해결 방법

`apps/edge/internal/openai` package에 small helper를 추가한다. helper는 `s.cfg.ProviderAuth`를 읽고:

- disabled이면 `nil, nil`을 반환한다.
- enabled이면 `FromHeader`에서 raw token을 읽는다.
- raw token이 비어 있고 `Required`이면 sentinel error를 반환한다.
- `Scheme`이 비어 있지 않고 raw token이 이미 같은 scheme으로 시작하지 않으면 `<scheme> <raw>` 형식으로 만든다.
- 반환 map은 `{TargetHeader: value}`만 포함한다.

`submitChatCompletionTunnel`에서는 `SubmitProviderTunnelRequest` 생성 전에 helper를 호출한다. missing-required error는 `SubmitProviderTunnel` 호출 전에 `400 invalid_request_error`로 반환하며, raw token을 error/log/metadata에 쓰지 않는다. 성공 시 `Headers: headers`를 tunnel request에 넣는다.

#### 체크리스트

- [ ] `apps/edge/internal/openai/stream.go` 또는 같은 package의 작은 helper 파일에 provider auth helper를 추가한다.
- [ ] `submitChatCompletionTunnel`에서 helper를 dispatch 전에 호출한다.
- [ ] `SubmitProviderTunnelRequest.Headers`에 helper 결과를 넣는다.
- [ ] missing required provider auth는 `SubmitProviderTunnel` 전에 400으로 끝내고 raw token을 echo하지 않는다.

#### 중간 검증

```bash
go test ./apps/edge/internal/openai -run 'TestProviderTunnelAuthHeaders|TestChatProviderTunnelProviderAuth|TestChatProviderTunnelForwardsProviderAuthHeader' -count=1
```

### [REVIEW_SEULGI_TUNNEL-2] Handler Tests

#### 문제

SDD S03을 증명하는 handler tests가 없다. review-time targeted test는 `[no tests to run]`이었다.

#### 해결 방법

`apps/edge/internal/openai/server_test.go`에 다음 케이스를 추가한다.

- `TestChatProviderTunnelForwardsProviderAuthHeader`: `openai.provider_auth.enabled=true`, caller header `X-IOP-Provider-Authorization: user-token`, captured tunnel request `Headers["Authorization"] == "Bearer user-token"`.
- `TestChatProviderTunnelProviderAuthRequired`: required provider auth header가 없으면 response 400, fake service의 `tunnelReqsSnapshot()` 길이 0.
- `TestChatProviderTunnelProviderAuthDoesNotReplaceInboundAuth`: `openai.bearer_token`과 provider auth가 함께 설정된 상태에서 inbound `Authorization`은 IOP auth로만 쓰고 provider header는 configured provider auth header에서 만든다.

필요하면 helper formatting test를 별도로 추가하되, handler-level tests가 raw `Bearer token` already-prefixed value를 보존하는지도 확인해야 한다.

#### 체크리스트

- [ ] provider auth forwarding success test를 추가한다.
- [ ] missing-required no-dispatch test를 추가한다.
- [ ] inbound IOP auth와 outbound provider auth 분리 test를 추가한다.
- [ ] targeted test 출력에 `[no tests to run]`이 남지 않게 한다.

#### 중간 검증

```bash
go test ./apps/edge/internal/openai -run 'TestChatProviderTunnelProviderAuth|TestChatProviderTunnelForwardsProviderAuthHeader|TestChatProviderTunnelProviderAuthDoesNotReplaceInboundAuth' -count=1
```

### [REVIEW_SEULGI_TUNNEL-3] Verification Evidence And Review Stub

#### 문제

이전 `CODE_REVIEW-cloud-G07.md`는 구현 항목별 완료 여부, 구현 체크리스트, 계획 대비 변경 사항, 주요 설계 결정, 검증 결과가 비어 있었다. 구현이 있어도 리뷰가 검증 신뢰를 확보할 수 없는 상태다.

#### 해결 방법

구현 후 active `CODE_REVIEW-cloud-G07.md`에서 구현 에이전트 소유 섹션만 채운다. 검증 결과에는 실제 stdout/stderr를 붙인다. 기본 `go test`가 `/config/.cache/go-build` 권한으로 실패하면 그 실패 출력도 기록하고, 같은 package/test arguments를 유지한 채 temporary `GOCACHE`로 재실행한 출력도 함께 기록한다.

#### 체크리스트

- [ ] 구현 항목별 완료 여부 table을 실제 완료 상태로 체크한다.
- [ ] 구현 체크리스트를 실제 완료 상태로 체크한다.
- [ ] 계획 대비 변경 사항과 주요 설계 결정을 placeholder 없이 채운다.
- [ ] targeted test와 final test의 실제 stdout/stderr를 기록한다.

#### 중간 검증

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

## 최종 검증

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

Expected:

- provider auth 관련 targeted tests가 실제로 실행된다.
- 전체 `apps/edge/internal/openai` package test가 통과한다.
- 검증 출력과 구현 내용이 active `CODE_REVIEW-cloud-G07.md`에 기록되어 있다.
