---
spec_doc_type: spec
spec_id: runtime/stream-evidence-gate
status: 구현됨
source_evidence:
  - type: contract
    path: agent-contract/outer/openai-compatible-api.md
    notes: OpenAI-compatible ingress, stream commit와 terminal 오류 계약
  - type: contract
    path: agent-contract/inner/edge-config-runtime-refresh.md
    notes: Stream Evidence Gate 설정과 restart-required 분류 계약
  - type: code
    path: packages/go/streamgate/runtime.go
    notes: request-local event, evaluation, release와 recovery lifecycle
  - type: code
    path: packages/go/streamgate/recovery_coordinator.go
    notes: abort, optional prepare, rebuild, budget consume와 single dispatch 순서
  - type: code
    path: apps/edge/internal/openai/stream_gate_runtime.go
    notes: OpenAI Chat과 provider tunnel host wiring
  - type: code
    path: apps/edge/internal/openai/openai_request_rebuilder.go
    notes: bounded lossless OpenAI request rebuild
  - type: test
    path: packages/go/streamgate/runtime_test.go
    notes: Core lifecycle, recovery와 terminal 검증
  - type: test
    path: apps/edge/internal/openai/stream_gate_vertical_slice_test.go
    notes: Edge 실제 admission과 full-cycle 검증
  - type: test
    path: apps/edge/internal/openai/stream_gate_pipeline_test.go
    notes: Chat/Responses tunnel의 exact-wire terminal, split tool identity, non-2xx lifecycle 검증
  - type: test
    path: apps/edge/internal/openai/stream_gate_stall_recovery_test.go
    notes: S05 endpoint/path/semantic recovery matrix, shared budget, candidate identity, transport close, guard terminals, and disabled-semantic compatibility
  - type: test
    path: apps/edge/internal/openai/filter_observation_sink_test.go
    notes: raw-free observation allowlist와 correlation 검증
  - type: test
    path: apps/edge/internal/openai/liveness_recovery_observability_test.go
    notes: request-local closed-label liveness metrics, safe default-log projection, and explicit-sink forwarding
  - type: code
    path: apps/edge/internal/openai/single_request_anthropic_stream.go
    notes: Separate marked service-to-Anthropic progress/ping/terminal projector that does not enter the generic gate
  - type: test
    path: apps/edge/internal/openai/single_request_anthropic_stream_test.go
    notes: Exact-wire evidence that the marked projector remains isolated from generic gate semantics
---

# 스펙: Stream Evidence Gate

## 목적

codec이 정규화한 provider event를 downstream에 쓰기 전에 evidence와 모든 활성 filter 결과를 확인하고, 한 번의 release·terminal·recovery 결정으로 수렴시키는 현재 request-local runtime을 설명한다.

## 기능 목록

| 기능 | 설명 |
|------|------|
| normalized event contract | response-start, text/reasoning, tool-call, terminal, provider-error event를 transport와 분리하고, Chat/Responses tunnel의 caller wire는 semantic evidence와 별도 queue에서 원본 순서로 release한다. |
| evidence hold | 기본 500 Unicode rune rolling window와 bounded terminal/fragment gate를 사용하며 안전한 prefix만 release한다. |
| staged commit | status/header/opening event를 첫 safe release까지 보류하고 `transport_uncommitted`, `stream_open`, `terminal_committed` 상태에서 terminal을 한 번만 commit한다. |
| filter registry와 arbitration | request 시작의 registry/config generation을 고정하고 attempt별 active filter를 다시 해석한 뒤 병렬 결과를 모두 모아 deterministic action 하나를 선택한다. |
| bounded recovery | exact replay, continuation repair, schema repair는 request-total·strategy별 fault cap 안에서 실행하며 managed continuation은 별도 trajectory budget을 사용한다. |
| request rebuild | 기본값이자 절대 상한 16 MiB의 ingress snapshot에서 OpenAI JSON unknown field를 보존하고 필요한 typed subtree만 bounded patch한다. |
| repeat-resume builder | A selected continuation plan can consume one request-local content/reasoning snapshot and build endpoint-native Chat or Responses resume input with the fixed English directive, without caller history or another model call. |
| active repeat guard | Request-local Chat/Responses history fingerprints, a Unicode rolling pending window, and committed look-behind produce sanitized pass, continuation, repeated-action safe-stop, or side-effect fatal decisions. |
| host re-admission | 현재 provider ownership을 닫은 뒤 optional one-shot prepare, rebuild, budget consume, 단일 dispatch 순서로 새 actual model/provider/path binding을 설치한다. |
| raw-free observation | request correlation, attempt/epoch, filter/rule, decision, recovery와 bounded sanitized cause/evidence만 timeline sink로 보낸다. The OpenAI liveness projection additionally emits one closed eligibility metric and at most one closed final-result metric per private cycle. |
| typed stall handoff | Every supported OpenAI Chat/Responses normalized or tunnel request has one always-on runtime liveness owner. It maps only an Edge-confirmed `response_stalled` terminal to a raw-free provider error and evaluates ExactReplay through the existing commit/cancel/side-effect/snapshot/shared-budget contract. |
| separate marked Anthropic projection | The single-request coordinator's fixed plan/work/review/repair summaries, `event: ping`, content indices, and endpoint terminal are owned by a separate serialized service-to-endpoint projector. They do not become normalized gate events, filters, release decisions, or recovery inputs. |

## 범위

- 포함: transport-agnostic Core, OpenAI-compatible Chat와 normalized non-stream Responses runtime, provider-pool mixed path, Chat/Responses streaming provider tunnel, request-local ingress/rebuild와 Edge observation sink.
- 제외: 반복·missing tool-call·schema 같은 semantic detector 자체, provider/model 선택 알고리즘, raw parser, cross-request 저장과 범용 오류 수정 workflow, marked single-request Anthropic progress/ping/terminal projection.

## 주요 흐름

```mermaid
sequenceDiagram
  participant Host as Edge host
  participant Core as Stream Gate Core
  participant Filters
  participant Provider
  participant Sink as Release sink

  Host->>Core: request snapshot + initial AttemptBinding
  Provider->>Core: normalized response-start/event
  Core->>Core: stage + evidence hold
  Core->>Filters: immutable EvidenceBatch 병렬 평가
  Filters-->>Core: all outcomes
  alt release 또는 terminal
    Core->>Sink: safe release / single terminal
  else recovery
    Core->>Provider: current attempt abort
    Core->>Host: optional prepare + bounded rebuild
    Host->>Provider: single re-admission
    Provider->>Core: next attempt events
  end
```

## 계약

- 외부 OpenAI-compatible 오류와 stream framing: `agent-contract/outer/openai-compatible-api.md`
- Marked Anthropic stream subset: `agent-contract/outer/anthropic-compatible-api.md`
- Edge 설정과 refresh 분류: `agent-contract/inner/edge-config-runtime-refresh.md`
- Edge-Node provider tunnel wire: `agent-contract/inner/edge-node-runtime-wire.md`

## 설정/데이터/이벤트

- `openai.stream_evidence_gate.enabled` defaults to `false` and controls only configured semantic filters and their capability admission. The Core owns the supported response/liveness lifecycle in both states, while disabled mode preserves endpoint-native compatibility through runtime adapters.
- `max_request_fault_recovery`는 0..3, `max_strategy_fault_recovery`는 0..request-total이고 생략 시 request-total을 상속한다. base 또는 selector에서 활성화될 수 있는 blocking `repeat_guard`는 두 상한이 모두 최소 1이어야 한다.
- `max_ingress_snapshot_bytes`는 1..16777216이며 생략 시 16 MiB다. raw body limit은 첫 read 전에 적용되고 canonical body, typed view와 rebuild peak가 같은 request-local ledger에 포함된다.
- Stream Evidence Gate 설정 변경은 현재 restart-required다. request가 시작된 뒤 config/registry snapshot은 바뀌지 않는다.
- The production Core registry includes the common Noop filter, configured active `repeat_guard`, schema/provider-error lifecycle foundations, and applicable request-local tool validation. Repeat detection uses the configured 500-rune default, never time-based release, and returns a continuation only before a tool/side-effect boundary. Provider-error still records unmatched errors as pass until its matcher Task.
- The private typed-stall evaluator is always registered for supported requests and is independent from configured semantic `filters[]` and provider capability admission. It closes a confirmed old transport without a duplicate cancel and passes the failed provider once to pool re-admission; only `available` permits avoided-provider fallback.
- Liveness metrics use only `execution_path`, `provider_health`, `commit_state`, `eligibility`, and `recovery_result` closed vocabularies. Constructor-owned generic zap logging is replaced for the private liveness/ExactReplay rows with a safe projection; a sink supplied through `SetObservationSink` still receives the original immutable observations.
- Resume recording is bounded by the ingress snapshot limit and is reset for every attempt. The Rebuilder consumes it once after the owning attempt is aborted. It uses the request-start model catalog context window and fails before dispatch when the window is unknown or the rebuilt prompt plus its completion reserve does not fit.
- A repeat continuation cursor is a UTF-8 byte boundary for content or reasoning. Already committed look-behind fixes the cursor at the released channel boundary; the pending duplicate is discarded, and a byte-identical replacement prefix is suppressed once. Omitted temperature uses `0.2`, `0.4`, and `0.6` by strategy attempt; explicit temperature is preserved.
- Marked single-request Anthropic progress consumes only the coordinator's closed public enum in its endpoint projector. Its pings and terminal lock do not pass through the Core registry, mutate request-start gate snapshots, or enable generic filters/recovery.

## 검증

- `make proto` - generated protobuf가 현재 schema와 일치해야 한다.
- `go test -race -count=1 ./packages/go/streamgate ./apps/edge/internal/openai ./packages/go/config` - Core, Edge vertical cycle, Chat/Responses tunnel exact-wire terminal·split tool identity·non-2xx lifecycle, request rebuild, observation과 config 경계가 통과해야 한다.
- `git diff --check` - 문서와 코드 diff에 공백 오류가 없어야 한다.

## 한계와 주의사항

- Normalized `/v1/responses` does not support streaming, but it always uses the request-local StreamGate runtime. Supported Chat/Responses provider tunnels also separate protocol finish from the transport terminal and release trailing wire once.
- Direct provider-tunnel non-stream responses retain buffered passthrough compatibility inside the same runtime. The ingress bound applies independently of semantic-filter activation.
- Always-on Core ownership does not automatically activate a semantic filter.
- The repeat detector remains a separately configured filter. The implemented builder is only the request-local continuation seam; it does not translate, summarize, or use a local model or `RecoveryPlanPreparer`.
- observation은 저장소가 아니라 event envelope이며 보존·조회 정책은 host observability sink가 소유한다.
- The marked Anthropic projector's privacy and exactly-once guarantees are verified independently. They must not be cited as evidence that a generic Stream Evidence Gate filter or recovery strategy ran.

## 변경 기록

- 2026-07-28: Stream Evidence Gate Core 완료 감사에서 현재 Core, Edge wiring, 계약과 테스트 근거로 targeted spec 생성.
- 2026-07-28: Chat/Responses tunnel의 terminal wire queue, split tool identity와 non-2xx provider-error lifecycle 근거로 normalized Responses runtime 범위와 foundation 한계를 현재 구현에 맞췄다.
- 2026-07-28: Added the request-local Chat/Responses repeat-resume builder, its bounded recorder lifecycle, fixed directive, caller-history exclusion, and context-window fail-closed boundary.
- 2026-07-29: Activated request-local history/current-stream repeat detection, Unicode safe cursors, no-progress action safe-stop, one-shot prefix suppression, and continuation temperature candidates.
- 2026-08-05: Added raw-free `response_stalled` mapping and runtime-local confirmed-handoff recovery ownership for OpenAI StreamGate attempts.
- 2026-08-05: Made supported Chat/Responses normalized and tunnel liveness ownership unconditional, isolated semantic activation to configured filters/capability admission, and added deterministic S05 recovery/guard/compatibility evidence.
- 2026-08-06: Added request-local liveness eligibility/result metrics and constructor-default-only safe observation-log projection.
- 2026-08-06: Recorded the marked single-request Anthropic projector as a separate service-to-endpoint boundary without expanding generic gate events, filters, release, recovery, or observation semantics.
