# SDD: [surface-01] Inference API Surface와 실행 Lifecycle 책임 경계 리팩터링 ## 위치 - Milestone: [Milestone 문서](../../../phase/knowledge-tool-optimization-extension/milestones/inference-api-surface-execution-lifecycle-refactor.md) - Phase: [PHASE.md](../../../phase/knowledge-tool-optimization-extension/PHASE.md) ## 상태 [확정] ## 결정 사항 - 결정 기록: - [x] [D01] OpenAI, Anthropic, Gemini wire contract는 공통 DTO로 합치지 않고 각 surface adapter에 남긴다. - [x] [D02] provider admission·dispatch·attempt·cancel은 기존 `runService`/`SubmitProviderPool` 경계를 재사용하고, protocol별 실행 준비와 endpoint commit·terminal은 각 기존 runtime이 소유한다. 하나의 universal lifecycle owner나 terminal DTO를 만들지 않는다. - [x] [D03] 리팩터링은 caller-visible behavior, provider selection, native option passthrough와 benchmark 조건을 바꾸지 않는다. - [x] [D04] `iop-s0` `[bench-02]` 결과가 있으면 리팩터링의 비교 baseline으로 사용하되, 결과 상태나 workspace lock을 구현 선행 조건으로 사용하지 않는다. - [x] [D05] benchmark 비교가 현재 작업 범위에 포함되면 제품 code/spec/contract와 benchmark-only harness/data의 차이를 분리한다. - [x] [D06] 완료 evidence는 deterministic characterization과 기존 benchmark artifact 비교를 기본으로 한다. 외부 provider live smoke나 benchmark 재실행은 별도 환경·비용 승인이 있을 때만 추가한다. ## 문제 / 비목표 - 문제: 현재 Edge는 `apps/edge/internal/service`의 `SubmitProviderPool`/`SubmitProviderTunnel`로 provider admission과 dispatch를 이미 공통화했지만, ingress orchestration과 endpoint lifecycle 책임은 `apps/edge/internal/openai` 안에서 경로별로 다르게 조립된다. 특히 Gemini ingress는 Chat request와 내부 HTTP 요청/response writer를 합성해 `handleChatCompletions`에 재진입하므로 Chat handler의 validation·오류·commit 방식에 간접 결합된다. Anthropic native/bridge, OpenAI passthrough/normalized, single-request는 서로 다른 terminal contract를 가지므로 기존 service 경계를 넘어 request/terminal schema까지 공통화하면 회귀 위험이 크다. - 비목표: - 서로 다른 외부 API field와 event를 하나의 범용 request/response schema로 축소 - 신규 기능, provider 최적화, routing/retry/filter 정책 변경 - Control Plane, Edge-Node wire, config/proto 계약 변경 - benchmark pipeline 또는 비교 결과 수정 - 기존 `SubmitProviderPool`과 겹치는 새 all-surface executor 또는 모든 endpoint terminal을 소유하는 중앙 state machine 도입 ## Source of Truth | 영역 | 기준 | 메모 | |------|------|------| | Roadmap | [Milestone 문서](../../../phase/knowledge-tool-optimization-extension/milestones/inference-api-surface-execution-lifecycle-refactor.md) | 범위와 Task 원장 | | API Contracts | [OpenAI-Compatible API](../../../../agent-contract/outer/openai-compatible-api.md), [Anthropic-Compatible Messages API](../../../../agent-contract/outer/anthropic-compatible-api.md), [Gemini-Compatible API](../../../../agent-contract/outer/gemini-compatible-api.md) | caller-visible 동작 baseline | | Current Spec | [OpenAI-Compatible 입력 표면](../../../../agent-spec/input/openai-compatible-surface.md), [Stream Evidence Gate](../../../../agent-spec/runtime/stream-evidence-gate.md) | 현재 구현 책임과 lifecycle 설명 | | Surface/Ingress Code | `apps/edge/internal/openai/` | surface codec, validation, projector, Stream Evidence Gate와 operation-specific ingress orchestration 구현 | | Provider Execution | `apps/edge/internal/service/`의 `SubmitProviderPool`, `SubmitProviderTunnel`, `SubmitRun` | provider admission·dispatch·attempt·Node cancellation의 기존 source of truth | | Benchmark Baseline | [iop-s0 `[bench-02]` Milestone](../../../../../iop-s0/agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/iop-one-shot-agent-model-comparison.md) | 구현 시작을 잠그는 비교 work owner와 최종 evidence | | User Decision | 없음 | D01-D06은 기존 code/contract와 사용자 요청 범위에서 도출한 기술 기준이며 신규 제품 결정은 없다. | ## State Machine | 상태 | 진입 조건 | 다음 상태 | 근거 | |------|-----------|-----------|------| | `surface_received` | endpoint가 request와 surface auth header를 수신 | `surface_validated`, `terminal_error`, `cancelled` | surface adapter가 credential form과 body를 소유 | | `surface_validated` | surface validation, 공통 principal resolver와 route snapshot 완료 | `admitted`, `terminal_error`, `cancelled` | surface adapter + shared auth/projection resolver | | `admitted` | immutable protocol-specific execution input이 준비됨 | `attempting`, `terminal_error`, `cancelled` | ingress orchestration이 기존 service request를 구성 | | `attempting` | 기존 service가 provider candidate와 operation을 확정하고 attempt 시작 | `pre_commit`, `terminal_error`, `cancelled` | `SubmitProviderPool`/`SubmitProviderTunnel` | | `pre_commit` | endpoint runtime이 아직 caller-visible byte를 commit하지 않음 | `committed`, `terminal_error`, `cancelled` | Stream Evidence Gate, endpoint projector 또는 single-request coordinator | | `committed` | endpoint-native status/header/opening event가 최초 commit됨 | `terminal_success`, `terminal_error`, `cancelled` | surface별 wire writer/terminal state machine | | `terminal_success` | endpoint-native success terminal과 해당 경로의 usage finalization 완료 | 종료 | endpoint runtime의 exactly-once terminal evidence | | `terminal_error` | pre-commit envelope 또는 post-commit endpoint-native error terminal 확정 | 종료 | surface error projection과 기존 terminal winner | | `cancelled` | caller context 취소가 해당 경로의 terminal winner가 됨 | 종료 | endpoint가 service/Node cancel을 전파하고 이후 wire write 금지 | State invariant: - 한 public request의 각 lifecycle 전이에는 owner가 하나만 있고 endpoint terminal winner도 하나만 존재한다. 모든 전이를 하나의 새 중앙 owner로 이동하지 않는다. - surface adapter는 credential form, decode/validation/error/SSE 형식을 소유한다. 공통 auth/projection resolver와 service provider selection/attempt identity/Node cancellation은 중복 구현하지 않는다. - operation-specific ingress 실행 함수는 OpenAI/Anthropic/Gemini public DTO나 `http.ResponseWriter`를 `apps/edge/internal/service`로 넘기지 않는다. 기존 service request/result를 사용하고 endpoint projector hook도 service에 주입하지 않는다. - Stream Evidence Gate와 single-request coordinator는 기존 pre-commit/terminal state machine을 계속 소유하며 새 universal terminal DTO로 치환하지 않는다. - raw passthrough body는 route에 필요한 bounded field만 읽고 unknown/provider-native field를 제거하지 않는다. translated body도 `extra_body`를 포함한 생성 결과를 손실 없이 전달한다. - commit 전후의 오류 projection은 surface 계약을 따르며, 다른 surface의 handler나 HTTP round-trip을 통해 얻지 않는다. ## Interface Contract - 계약 원문: [OpenAI-Compatible API](../../../../agent-contract/outer/openai-compatible-api.md), [Anthropic-Compatible Messages API](../../../../agent-contract/outer/anthropic-compatible-api.md), [Gemini-Compatible API](../../../../agent-contract/outer/gemini-compatible-api.md) - 입력: - authenticated request snapshot: principal, public route/model, immutable projection/binding revision과 cancellation context다. - protocol operation: Chat Completions, Responses, Anthropic Messages 또는 surface가 명시적으로 변환한 upstream operation이다. - response mode: buffered, normalized stream 또는 raw tunnel stream의 실행 의미다. - execution payload: raw passthrough body 또는 surface가 검증해 만든 provider request body/normalized input이다. - 출력: - 기존 service result: selected execution path, caller에 노출하지 않는 attempt attribution과 run/tunnel handle이다. - endpoint runtime input: 기존 RunEvent/tunnel frame/single-request progress 중 해당 operation이 이미 사용하는 bounded 결과다. - endpoint terminal: surface projector나 기존 coordinator가 자기 error/terminal contract로 확정하며 공통 service 결과 타입으로 만들지 않는다. - 금지: - Gemini가 내부 `http.Request`/`ResponseWriter`를 만들어 Chat handler를 호출한다. - 공통 service가 Gemini/Anthropic/OpenAI public response envelope나 SSE event 이름을 생성한다. - endpoint projector callback이나 `http.ResponseWriter`를 provider service interface에 추가한다. - 기존 `SubmitProviderPool`과 같은 capability를 포장만 바꿔 중복 구현한다. - 공통 DTO를 만들기 위해 surface-specific field, unknown field 또는 provider-native option을 버린다. - 리팩터링 중 provider fallback, retry budget, output filter, single-request stage semantics를 변경한다. ## Acceptance Scenarios | ID | Milestone Task | Given | When | Then | |----|----------------|-------|------|------| | S01 | `baseline-freeze` | 현재 동작을 비교할 기준이 필요함 | 기존 characterization 또는 사용 가능한 benchmark 결과를 선택 | target/current revision과 제품 delta, benchmark-only delta가 구분된다. benchmark 상태와 workspace lock은 선행 조건이 아니다. | | S02 | `boundary-map` | 세 외부 API와 raw/normalized/single-request 경로가 존재 | 책임/의존 지도를 검토 | surface, shared auth resolver, 기존 service, endpoint runtime의 단계별 소유권이 겹치지 않고 API/terminal DTO 공통화가 비범위로 표시된다. | | S03 | `execution-entrypoint` | Gemini direct와 virtual preset 요청 | Gemini ingress를 실행 | Chat handler 내부 HTTP 재진입이나 중복 service port 없이 기존 provider/preset execution capability를 호출하고 Gemini contract 결과를 반환한다. | | S04 | `surface-adapters` | 각 surface의 정상·validation·auth·provider 오류 입력 | endpoint별 adapter/projector를 실행 | 외부 status, error envelope, model echo와 SSE event 순서가 baseline과 동일하다. | | S05 | `lifecycle-owner` | 정상, provider 오류, timeout, pre/post-commit failure, caller cancel | stream/non-stream lifecycle을 종료 | 단계별 owner가 하나이고 public terminal과 request usage가 exactly once이며 취소 뒤 추가 wire write가 없다. | | S06 | `native-option-preservation` | unknown OpenAI field, Anthropic native extension, Gemini thinking `extra_body`와 model alias | raw 또는 translated dispatch를 수행 | selected provider body에 허용 field가 보존되고 unsupported 의미는 기존 provider/surface 오류로 귀결된다. | | S07 | `contract-regression` | OpenAI Chat/Responses, Anthropic Messages, Gemini direct/single-request characterization matrix와 기존 benchmark artifact | deterministic package test와 artifact 비교를 실행 | caller-visible behavior와 benchmark admission 조건에 의도하지 않은 차이가 없고 별도 승인 없는 live provider 호출은 발생하지 않는다. | | S08 | `spec-sync` | 리팩터링 구현과 회귀 검증 완료 | contract source pointer와 implementation spec을 점검 | 외부 계약 의미는 유지되고 source path·책임 경계·현재 스펙이 실제 코드와 일치하며 기존 계약과 다른 제품 동작은 별도 후보로 분리된다. | ## Cross-repo Reference - 참고: [iop-s0 `[bench-02]` IOP 원샷 Agent 모델 비교 벤치마크](../../../../../iop-s0/agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/iop-one-shot-agent-model-comparison.md) - 사용 가능한 결과가 있으면 비교 자료로 사용한다. 해당 Milestone의 상태나 workspace lock은 이 구현의 선행 조건이 아니다. ## 사용자 리뷰 이력 - 2026-08-13: API 방식 자체가 달라 범용 request/terminal interface가 어렵다는 사용자 문제 제기를 바탕으로, benchmark 완료 뒤 구현하되 현시점에는 surface 계약 분리와 기존 provider service 경계 재사용을 기준으로 Milestone을 먼저 작성했다. ## 작업 컨텍스트 - 표준선: surface-specific anti-corruption adapter + existing provider service capability + transition별 single owner. Operation-specific ingress 함수는 필요한 경로에만 추출하고 물리 package 이동은 의존 방향을 검증하는 최소 범위로 제한한다. - 후속 SDD: 없음