# SDD: OpenAI-compatible Provider Passthrough 계약 동기화

## 위치

- Milestone: [Milestone 문서](../../../phase/routing-policy-model-orchestration/milestones/openai-compatible-provider-passthrough-contract-sync.md)
- Phase: [PHASE.md](../../../phase/routing-policy-model-orchestration/PHASE.md)

## 상태

[승인됨]

## SDD 잠금

- 상태: 해제
- 사용자 리뷰: 없음
- 잠금 항목:
  - 없음

## 문제 / 비목표

- 문제: 현재 코드에는 caller metadata로 응답 경로를 선택하는 과거 설계와 provider-native OpenAI-compatible payload를 Edge가 제한하거나 IOP 확장 field로 치환하는 흐름이 남아 있다. 사용자 설계 의도는 `model`이 가리키는 provider capability가 passthrough/normalized 경로를 결정하고, OpenAI-compatible provider에서는 provider request surface를 그대로 보존하는 것이다.
- 비목표:
  - 새 route scorer 또는 hybrid local/cloud routing policy 구현
  - provider runtime launch/restart option 변경
  - output validation filter의 schema/repair policy 구현
  - archive 문서의 과거 결정 기록 재작성

## Source of Truth

| 영역 | 기준 | 메모 |
|------|------|------|
| Roadmap | [Milestone 문서](../../../phase/routing-policy-model-orchestration/milestones/openai-compatible-provider-passthrough-contract-sync.md) | 목표, 범위, 기능 Task, 완료 evidence 기준 |
| Outer Contract | [openai-compatible-api.md](../../../../../agent-contract/outer/openai-compatible-api.md) | OpenAI-compatible public request/response 계약 |
| Inner Wire Contract | [edge-node-runtime-wire.md](../../../../../agent-contract/inner/edge-node-runtime-wire.md) | Edge-Node provider tunnel body/frame 책임 |
| Code | `apps/edge/internal/openai`, `apps/edge/internal/service`, `apps/node/internal/adapters/openai_compat` | 구현 source of truth |
| External Provider | dev-corp Spark Ornith/vLLM provider 또는 dev gx10 Ornith/vLLM provider | provider-native field passthrough smoke 대상. 사용자가 dev gx10 동등 검증을 허용했다. |
| User Decision | 현재 사용자 설계 의도 | `model` 기반 route, metadata known-key read-only 발췌, provider-native payload 보존 |

## State Machine

| 상태 | 진입 조건 | 다음 상태 | 근거 |
|------|-----------|-----------|------|
| request-received | Edge OpenAI-compatible endpoint가 JSON body를 수신한다 | route-resolved 또는 invalid-request | `model` envelope parse |
| route-resolved | request `model`이 route catalog 또는 provider-pool model group에 매칭된다 | metadata-observed | selected provider capability |
| metadata-observed | route 결정 후 metadata가 있으면 known-key를 내부 문맥으로 복사한다 | provider-passthrough 또는 normalized-dispatch | read-only extraction, route/response path 불변 |
| provider-passthrough | selected provider가 OpenAI-compatible 호출 방식을 지원한다 | provider-response-relayed 또는 provider-error-relayed | ProviderTunnelRequest/ProviderTunnelFrame |
| normalized-dispatch | selected provider/backend가 normalized adapter 실행을 요구한다 | normalized-response-built 또는 run-error | RunRequest/RunEvent |
| provider-response-relayed | provider가 HTTP/SSE 성공 응답을 반환한다 | terminal success | provider status/header/body relay |
| provider-error-relayed | provider가 HTTP error 또는 tunnel error를 반환한다 | terminal error | provider status/body 또는 tunnel error |

## Interface Contract

- 계약 원문: [openai-compatible-api.md](../../../../../agent-contract/outer/openai-compatible-api.md)
- 입력:
  - `model`: route/provider capability 선택의 1차 source of truth
  - `messages` 또는 `input`: endpoint별 OpenAI-compatible request payload
  - `metadata`: IOP known-key를 read-only로 발췌하는 container. route/response selector가 아니다.
  - provider-native payload: selected provider가 지원하는 OpenAI-compatible 표준 field, extension field, 중첩 값. Edge passthrough 경로에서 보존한다.
- 출력:
  - provider-passthrough: provider HTTP status/header/body를 relay한다. Chat Completions의 top-level model echo는 alias 보정을 할 수 있다.
  - normalized-dispatch: normalized adapter 결과를 OpenAI-compatible response shape로 구성한다.
  - metrics/log: provider usage 후보와 IOP known-key 관측은 internal metric/log로 남긴다.
- 금지:
  - caller metadata로 passthrough/normalized/response shape를 선택하게 하지 않는다.
  - OpenAI-compatible provider passthrough body에 IOP marker/event/envelope를 주입하지 않는다.
  - Edge allowlist로 provider-native OpenAI-compatible extension field를 거부하지 않는다.
  - IOP 확장 field가 caller의 provider-native field를 삭제하거나 대체하지 않는다.
  - metadata known-key 발췌 과정에서 원본 provider payload를 strip/mutate하지 않는다.

## Acceptance Scenarios

| ID | Milestone Task | Given | When | Then |
|----|----------------|-------|------|------|
| S01 | `selector-remove` | caller가 metadata 안에 임의 response selector로 보일 수 있는 key를 넣는다 | Edge가 OpenAI-compatible request를 처리한다 | 해당 key는 route selector로 해석되지 않고, provider-passthrough body를 바꾸지 않는다 |
| S02 | `model-route` | request `model`이 OpenAI-compatible provider를 가리킨다 | Chat Completions 또는 Responses를 호출한다 | Edge는 provider tunnel passthrough를 사용한다 |
| S03 | `model-route` | request `model`이 CLI/Ollama/native normalized backend를 가리킨다 | Chat Completions 또는 Responses를 호출한다 | Edge는 normalized RunRequest path를 사용한다 |
| S04 | `metadata-known` | request metadata에 workspace/task/principal 관련 known key와 임의 key가 섞여 있다 | Edge가 route를 결정한 뒤 metadata를 처리한다 | known key는 내부 문맥으로 복사되고 원본 metadata payload는 제거/변형되지 않으며 임의 key는 route/response path를 바꾸지 않는다 |
| S05 | `field-preserve` | provider-pool 또는 direct OpenAI-compatible Chat request가 `chat_template_kwargs`와 임의 provider extension field를 포함한다 | Edge가 provider tunnel body를 만든다 | provider request body에는 `model` rewrite 외 field와 중첩 값이 보존된다 |
| S06 | `provider-error` | provider가 특정 extension field를 지원하지 않아 HTTP error를 반환한다 | Edge가 provider response를 relay한다 | caller는 provider status/body를 받으며 Edge가 자체 unsupported error로 선변환하지 않는다 |
| S07 | `policy-priority` | caller가 provider-native thinking field를 명시한다 | catalog generation policy 또는 IOP think 확장 처리가 실행된다 | caller provider-native field가 삭제되거나 대체되지 않는다 |
| S08 | `contract-sync` | 구현이 완료된다 | 계약/문서/로드맵 최신 문서를 검색한다 | archive를 제외한 최신 문서에는 public response selector field/모드 설명이 없다 |
| S09 | `go-tests` | 코드 변경이 완료된다 | 관련 Go 테스트를 실행한다 | Edge OpenAI handler, service provider-pool, Node OpenAI adapter 테스트가 통과한다 |
| S10 | `devcorp-smoke` | dev-corp Spark Ornith 또는 dev gx10 Ornith provider가 IOP OpenAI-compatible route에 연결되어 있다 | `chat_template_kwargs.enable_thinking=false` 요청을 direct provider와 IOP 경유로 각각 1회 호출한다 | provider-native think-off 동작과 일치하는 빠른 content-first 응답을 관찰하고 reasoning chunk가 생성되지 않는다 |

## Evidence Map

| Scenario | Required Evidence | `agent-task` 연결 | 완료 Evidence 기대 |
|----------|-------------------|------------------|---------------------------|
| S01 | selector metadata negative test, request body fixture | `agent-task/m-openai-compatible-provider-passthrough-contract-sync/...` | `Roadmap Completion`에 `selector-remove`와 test name |
| S02 | OpenAI-compatible provider route unit test | `agent-task/m-openai-compatible-provider-passthrough-contract-sync/...` | `Roadmap Completion`에 `model-route`와 provider tunnel dispatch evidence |
| S03 | normalized backend route unit test | `agent-task/m-openai-compatible-provider-passthrough-contract-sync/...` | `Roadmap Completion`에 `model-route`와 normalized dispatch evidence |
| S04 | metadata known-key parsing and body immutability test | `agent-task/m-openai-compatible-provider-passthrough-contract-sync/...` | `Roadmap Completion`에 `metadata-known`, route-stability, body immutability evidence |
| S05 | provider tunnel body preservation test | `agent-task/m-openai-compatible-provider-passthrough-contract-sync/...` | `Roadmap Completion`에 `field-preserve`와 captured body fixture |
| S06 | fake provider error relay test | `agent-task/m-openai-compatible-provider-passthrough-contract-sync/...` | `Roadmap Completion`에 `provider-error`와 status/body relay fixture |
| S07 | generation policy/provider-native priority test | `agent-task/m-openai-compatible-provider-passthrough-contract-sync/...` | `Roadmap Completion`에 `policy-priority`와 body diff evidence |
| S08 | `rg` evidence over non-archive docs/contracts/roadmap | `agent-task/m-openai-compatible-provider-passthrough-contract-sync/...` | `Roadmap Completion`에 `contract-sync`와 command output summary |
| S09 | Go test command output | `agent-task/m-openai-compatible-provider-passthrough-contract-sync/...` | `Roadmap Completion`에 `go-tests`와 pass summary |
| S10 | dev-corp 또는 dev gx10 single-call smoke output summary | `agent-task/m-openai-compatible-provider-passthrough-contract-sync/...` 또는 수동 검증 evidence | `Roadmap Completion` 또는 Milestone 완료 리뷰에 `devcorp-smoke`, direct/IOP first content latency, reasoning/content observation, IOP selected provider evidence |

## Cross-repo Dependencies

- 없음

## Drift Check

- [x] Milestone 기능 Task와 Acceptance Scenario가 일치한다.
- [x] Evidence Map이 code-review/complete.log에서 검증 가능하다.
- [x] agent-contract를 쓰는 경우 SDD에 계약 원문을 복제하지 않았다.
- [x] 사용자 리뷰가 필요한 항목은 `USER_REVIEW.md`에만 남겼다.

## 사용자 리뷰 이력

- 없음

## 작업 컨텍스트

- 표준선: OpenAI-compatible provider 경로는 provider-compatible proxy처럼 동작하고, provider-native field 판단은 provider에 맡긴다.
- 표준선: `model`이 route source of truth이며, metadata는 IOP known-key를 read-only로 발췌하는 container다.
- 표준선: provider tunnel body는 `model` served target rewrite와 auth/header 처리 외에는 caller/provider request surface와 중첩 값을 보존한다.
- 후속 SDD: 없음
