# SDD: [bench-route-01] 벤치 경로 최소 HTML 스모크

## 위치

- Milestone: [Milestone 문서](../../../../archive/phase/knowledge-tool-optimization-extension/milestones/benchmark-route-minimal-html-smoke.md)
- Phase: [PHASE.md](../../../../phase/knowledge-tool-optimization-extension/PHASE.md)

## 상태

[승인됨]

## SDD 잠금

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

## 문제 / 비목표

- 문제: OpenCode의 표준 Chat tool continuation에서 Gemini 전용 `thought_signature`가 유실되어 두 번째 호출이 HTTP 400으로 실패한다. caller별 보정 없이 선택된 provider profile이 이 메타데이터 왕복을 소유해야 한다.
- 비목표:
  - benchmark runner, retry, manifest, state store를 만들지 않는다.
  - GPT, Claude, ornith-fast 등 non-Gemini provider의 tool-call wire를 바꾸지 않는다.
  - OpenCode 또는 다른 caller 이름을 routing/normalization selector로 쓰지 않는다.

## Source of Truth

| 영역 | 기준 | 메모 |
|------|------|------|
| Roadmap | [Milestone 문서](../../../../archive/phase/knowledge-tool-optimization-extension/milestones/benchmark-route-minimal-html-smoke.md) | 9개 얇은 경로와 실패 경로만 재검증하는 범위 |
| Code | `packages/go/config/protocol_profile.go`, `apps/edge/internal/openai/provider_model_rewrite.go` | operation-scoped tool-call wire와 요청·응답 왕복 정규화 |
| External Provider | Gemini OpenAI-compatible Chat | Tool call을 이어갈 때 opaque thought signature를 요구한다. |
| User Decision | 없음 | agy를 제외하고 같은 두 자리에 OpenCode를 넣는 범위가 확정됐다. |

## State Machine

| 상태 | 진입 조건 | 다음 상태 | 근거 |
|------|-----------|-----------|------|
| provider tool response | selected profile의 Chat tool-call wire가 `gemini_openai_chat`이고 signature가 있다 | caller replay | Edge가 signature를 opaque tool-call id로 캡슐화한다. |
| caller replay | caller가 opaque id를 assistant/tool history에 변경 없이 보낸다 | provider continuation | Edge가 원래 id와 signature를 복원한다. |
| non-Gemini/no-signature | 다른 wire이거나 signature가 없다 | passthrough | 기존 요청·응답을 변경하지 않는다. |
| malformed opaque id | IOP prefix가 있지만 payload가 유효하지 않다 | fail-closed | provider dispatch 전에 요청을 거부한다. |

## Interface Contract

- 계약 원문: [OpenAI-Compatible API](../../../../../agent-contract/outer/openai-compatible-api.md), [Edge Config And Runtime Refresh](../../../../../agent-contract/inner/edge-config-runtime-refresh.md)
- 입력:
  - `normalization.tool_calls[operation].wire`: provider tool-call metadata normalization 선택자
  - `tool_calls[].id` / `tool_call_id`: caller가 왕복 보존하는 표준 id 표면
- 출력:
  - Gemini signature가 있는 tool call은 signature를 별도 extension으로 노출하지 않고 opaque id를 반환한다.
- 금지:
  - caller/SDK 이름으로 분기하지 않는다.
  - effort mapping을 tool-call metadata selector로 재사용하지 않는다.
  - non-Gemini profile 응답을 변환하지 않는다.

## Acceptance Scenarios

| ID | Milestone Task | Given | When | Then |
|----|----------------|-------|------|------|
| S01 | `minimal-html-calls` | OpenCode가 Gemini direct route에서 tool을 호출한다 | tool result를 같은 Chat history로 재전송한다 | Edge가 signature를 복원하고 provider continuation이 성공한다. |
| S02 | `failed-path-fixes` | Gemini와 non-Gemini protocol profile | 동일 signature 모양의 응답 및 malformed opaque id를 처리한다 | Gemini wire만 왕복하고 non-Gemini는 무변경, malformed는 fail-closed다. |
| S03 | `thin-bench-handoff` | direct/hybrid를 포함한 9개 얇은 경로 결과 | 결과표를 갱신한다 | 호출 가능 여부와 남은 소유 경계만 남고 새 harness는 없다. |

## Evidence Map

| Scenario | Required Evidence | `agent-task` 연결 | 완료 Evidence 기대 |
|----------|-------------------|------------------|---------------------------|
| S01 | `provider_model_rewrite_test.go` HTTP two-turn regression과 OpenCode direct 실측 | `agent-task/m-benchmark-route-minimal-html-smoke/...` | `minimal-html-calls` evidence에 tool continuation과 marker 결과 |
| S02 | config/service/openai package tests | `agent-task/m-benchmark-route-minimal-html-smoke/...` | `failed-path-fixes` evidence에 Gemini-only/no-op/fail-closed 결과 |
| S03 | `agent-test/dev/iop-benchmark-route-minimal-html-smoke.md` | `agent-task/m-benchmark-route-minimal-html-smoke/...` | `thin-bench-handoff` evidence에 9개 결과와 남은 blocker |

## 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`에만 남겼다.

## 사용자 리뷰 이력

- 없음

## 작업 컨텍스트

- 표준선: provider normalization은 선택된 concrete protocol profile의 operation별 wire를 기준으로 하며 caller identity와 분리한다.
- 후속 SDD: 없음
