# IOP 벤치 경로 최소 HTML 스모크

## 목적

벤치 대상 9개 caller/model/route 조합에 같은 최소 `index.html` 생성 요청을 한 번씩 직접 보내 호출 경로만 빠르게 확인한다. 전용 runner, manifest, 자동 retry, browser gate와 품질 채점은 사용하지 않는다.

## 고정 요청

외부 asset과 JavaScript 없이 단일 `index.html`을 구현한다. 문서에는 `<!doctype html>`, `<title>IOP Route Smoke</title>`, `<h1>IOP_ROUTE_SMOKE_OK</h1>`이 정확히 한 번씩 있어야 한다. direct 경로는 빈 caller workspace의 파일을 확인한다. Edge-owned execution preset은 비공개 workspace가 완료 전에 정리되는 제품 계약을 따르므로 caller 로컬 파일이 아니라 terminal `decision.output`의 완료·marker를 확인한다.

## 사전 확인 — 2026-08-13

| 항목 | 결과 |
|---|---|
| Claude Code | 실제 원격 실행기 2.1.177 확인 |
| OpenCode | 현재 workspace 1.18.3 확인; command-scoped provider config 사용 |
| Codex | 실제 원격 실행기 0.146.0 확인 |
| managed CA | live Edge CA bundle 확인 |
| principal token | 원격 SOPS의 기존 두 token 사용; 별도 benchmark token 미사용 |
| public `/v1/models` | 두 기존 token 모두 HTTP 200, route 9개 확인 |
| ambient CA override | 호출 전후 unset 확인 |

## 경로 결과

| 경로 | 최초 1회 결과 | 재검증 | 분류 |
|---|---|---|---|
| Claude Code → Claude direct | 통과 | 없음 | 기존 성공 |
| Claude Code → Gemini direct | 통과 | 없음 | 기존 성공 |
| OpenCode → Gemini direct | 6초, 첫 `glob` tool 성공 뒤 두 번째 provider 호출 HTTP 400 | normalization 포함 dev 배포 뒤 11초 통과; `glob`/`write`/`read`, terminal marker와 exact `index.html` 확인 | 선택된 Gemini Chat profile의 `thought_signature`를 표준 caller history로 왕복하지 못한 IOP 결함 수정; agy는 구현 Agent 부적합으로 대상 제외 |
| Claude Code → GPT direct | provider HTTP 400 | operation normalization 반영 뒤 9초, caller success, `index.html` marker 1회 | IOP가 tools+effort를 Chat으로 보낸 결함 수정 확인 |
| Codex → GPT direct | 30초, `turn.failed`, 파일 없음 | 공식 설정대로 임시 `CODEX_HOME`, Responses 전용 provider, `CODEX_CA_CERTIFICATE`에 CA bundle을 사용해 10초 통과 | 측정 환경 결함: 첫 호출은 CA bundle 대신 Edge leaf 인증서를 사용 |
| Claude Code → Gemini execution preset | 184초, caller terminal success, caller workspace 파일 없음 | 새 배포에서 33초 caller 정상 종료; Plan/Work/Review, workspace write/read/list, artifact 3개 cleanup 성공 | default-selector dispatch 결함 해소; terminal 문구에 marker가 없는 것은 caller-visible 결과 판정과 분리 |
| OpenCode → Gemini execution preset | 7초, Plan 통과 후 Work `provider` 오류 | `ornith-fast` Node 복구 후 19초 통과; 이후 세션 한정 Work를 `ornith:35b`로 전환한 경로도 51초·caller terminal marker·오류 0 | 최초 실패는 Edge가 502 `provider unavailable`로 닫은 환경 결함; OpenCode·IOP 코드 결함 아님 |
| Claude Code → GPT execution preset | provider 전 `messages[1].role` 검증 거절, 입력 normalize 후 Work `validation` | `timings` normalize가 포함된 live release에서 통과; 세션 한정 Work `ornith:35b`에서 71초·caller terminal success·marker 확인·오류 0 | mid-conversation system 입력과 provider `timings` 정규화 누락이었으며 국소 수정 후 해소 |
| Codex → GPT execution preset | 16초, `turn.completed`, terminal marker 1회 | 없음 | 통과 |

추가 API 분리에서는 동일 principal의 최소 `/v1/responses`가 HTTP 200이었다. Codex direct도 사용자 설정과 로그인 상태를 배제한 임시 `CODEX_HOME`, Responses 전용 custom provider, 원격 SOPS의 기존 token, command-scoped managed CA bundle으로 통과했다. OpenCode direct는 command-scoped `OPENCODE_CONFIG_CONTENT`와 원격 SOPS의 기존 token을 사용해 user config를 변경하지 않았다. 최초 실행은 첫 `glob` tool 뒤 caller가 Gemini provider extension을 표준 Chat history에 보존하지 않아 다음 요청이 `thought_signature` 누락 HTTP 400으로 실패했다. 이는 OpenCode 전용 문제가 아니라 selected Gemini Chat provider normalization 누락으로 분류했다. IOP가 opaque signature를 선택된 Gemini Chat wire에서만 tool-call id에 캡슐화·복원하도록 수정한 dev 배포 뒤에는 `glob`/`write`/`read` 연속 호출, terminal marker, exact `index.html`이 11초 안에 모두 통과했다.

두 preset 최초 실패는 provider 자체 실패가 아니었다. 코드 대조에서 `default` resource selector가 의도적으로 빈 provider ID를 동결하는 반면 사후 검증은 pool이 정상 선택한 실제 provider ID와 무조건 같아야 한다고 요구한 결함을 확인했다. explicit selector의 provider ID와 profile/model/credential/path fence는 유지하고, default selector만 pool 선택을 인정했다. 재배포 뒤 Gemini preset은 전체 stage와 workspace lifecycle이 통과했다. GPT preset은 다음 경계까지 진행해 실제 OpenAI Chat 응답의 `service_tier`, `system_fingerprint`, `annotations`, null `refusal`을 private strict decoder가 거부하는 별도 normalize 누락을 드러냈다. 공통 provider-normalization 계층에서 Chat/Responses 결과를 같은 canonical stage envelope로 수렴시키는 국소 회귀가 통과했다.

그 수정 배포 뒤 최신 Claude Code는 첫 task 요청에 `messages=[user, system]`과 `mid-conversation-system-2026-04-07` beta를 보냈다. IOP는 beta header를 지원 목록에 두었지만 strict ingress와 logical-request lineage는 여전히 user/assistant만 허용했고, Chat/Responses bridge에도 system message 변환이 없었다. system 내용을 user text로 낮추지 않고 ordered privileged message로 보존하도록 공통 Anthropic 입력 normalize 경계를 수정했으며, 첫-position system, assistant 뒤 system, system 뒤 user, non-text system, 미완료 tool 결과 사이 삽입은 fail-closed로 유지했다.

해당 입력 normalize 배포 뒤 단일 재검증은 Plan 성공 후 Work `validation`으로 종료됐다. preset 구성상 Plan/Review는 GPT지만 Work는 `ornith-fast`다. RTX Ornith에 같은 forced workspace tool shape를 직접 보낸 결과 native `tool_calls`가 정상 반환됐고, 응답의 top-level provider `timings`가 기존 Work codec에서는 허용되지만 새 공통 Chat normalize allowlist에서 빠져 있음을 확인했다. 공통 normalize가 `timings`를 검증 후 폐기하도록 보완했다.

OpenCode → Gemini execution preset의 최초 실행은 Plan을 정상 통과했지만 Work에서 7초 만에 `provider` 오류로 닫혔다. live preset과 Edge 단계 지표를 대조한 결과 Work는 `ornith:35b`가 아니라 의도한 `ornith-fast`로 선택됐으며, 동일 Work request shape의 단일 직접 호출은 연결된 Node에 해당 provider가 없어 HTTP 502 `provider unavailable`를 반환했다. 사용자 관리형 RTX runtime을 toggle가 아닌 명시적 `Up`으로 복구한 뒤 실패 경로만 1회 재검증했고, 19초 내 caller terminal marker 1회·오류 0으로 통과했다. preset workspace가 caller 로컬에 파일을 남기지 않은 것은 기존 정리 계약과 일치한다.

사용자 지시에 따라 이 세션의 live execution preset만 Work 바인딩 네 곳을 `ornith-fast`에서 `ornith:35b`로 임시 전환했다. Plan/Review 모델과 tracked 설정은 변경하지 않았고, runtime config의 원본 백업을 남겼다. config check, refresh dry-run, apply가 재시작 없이 통과했다. 임시 경로에서 Claude Code → GPT execution preset은 71초, OpenCode → Gemini execution preset은 51초에 각각 terminal success와 caller-visible marker를 남겼고 오류는 없었다. 두 경로의 caller 로컬에 `index.html`이 남지 않은 것은 preset workspace 정리 계약과 일치한다.

## 완료 및 인계

Gemini Chat tool-call normalization 코드는 `dev` commit `d99305d1`과 merge `dc2d9968`로 반영됐고, 이를 포함한 source `de35e6d4`로 Edge와 macOS/Linux/Windows Node 네 대를 다시 빌드·배포했다. 빌드 전후 전체 Go package 49개가 각각 통과했고, 네 Node의 연결·provider health 및 OneX `ornith:35b`/RTX `ornith-fast`의 Chat·Responses capacity smoke 네 건도 별도 배포 검증으로 통과했다. 이 capacity smoke는 9개 얇은 경로의 성공 판정에는 섞지 않았다.

OpenCode → Gemini direct 재검증까지 끝나 9개 경로가 모두 통과했다. 새 benchmark script, runner, manifest 또는 retry state는 만들지 않았다. `[bench-lite-01]`은 이 9개 경로를 그대로 사용해 조합별 단일 시도 비교를 시작할 수 있다.

배포된 source는 원격 `archive/release-dev-1019-de35e6d`에 보존했다. 배포 직후 `origin/dev`에 별도 framework 동기화가 추가되고 다른 active release가 생겨 표준 release tag finish는 fail-closed 상태로 분리했다. 이는 live dev 배포와 9개 호출 성공에는 영향을 주지 않으며, 해당 release가 정리된 뒤 별도 Git bookkeeping으로 마쳐야 한다.

성공한 경로는 반복하지 않는다. 실패한 경로는 원인이 변경된 경우에만 해당 경로를 1회 재검증한다.
