- Refactor plan, code-review, finalize-task-routing, refine-local-plans, router skills - Add agent-workflow-loop-orchestration skill and plan agent configs - Update roadmap: knowledge-tool-optimization milestones, stream-evidence-gate-core SDD - Add stream-evidence-gate-core task, archive, and Go streamgate package - Update dev-test inventory (edge/node smoke), agent-contract, edge-local-dev-guide - Deprecate USER_REVIEW for output-validation-filters SDD
46 KiB
46 KiB
SDD: Stream Evidence Gate Core
위치
- Milestone: Milestone 문서
- Phase: PHASE.md
상태
[승인됨]
SDD 잠금
- 상태: 해제
- 사용자 리뷰: 없음
- 잠금 항목: 없음
문제 / 비목표
- 문제: 여러 stream consumer가 raw protocol parser, bounded tail, response-start staging, release/terminal commit과 recovery를 자체 구현하면 repeat loop, tool-call syntax, missing tool-call, schema, workflow terminal replacement마다 byte 경계·eager HTTP commit·중복
[DONE]·post-commit 복구 안전성이 달라진다. 또한 filter fan-out 동안 provider event를 계속 무제한 적재하거나 각 consumer가 자체 로그만 남기면 메모리 경계와 같은 request timeline의 decision을 보장할 수 없다. Core는 normalized event 이후의 hold/release, single-flight evaluation, strategy별 recovery와 sanitized observation mechanics를 한 번만 제공해야 한다. - 비목표:
- raw OpenAI SSE, CLI JSON, agent-family protocol을 직접 parse한다.
- 반복/문법/tool 필요성 같은 semantic detector 또는 LLM judge를 결정한다.
- filter가 개별 retry loop/counter, raw request mutation, provider selection 또는 dispatch를 소유한다.
- provider-pool의 후보 선택 알고리즘이나 filter별 corrective prompt의 의미를 Core가 결정한다.
- 기본 rolling 경로에서 전체 응답을 buffer하거나 terminal gate를 hard bound 없이 운영하거나 시간 경과만으로 미검증 event를 release한다.
- Edge provider-pool 또는 Node runtime의 구체 타입을 공통 Core package가 import한다.
- cross-request 오류를 사건으로 저장·중복 집계하거나, 연결된 소스 저장소를 LLM으로 분석해 수정 제안·프로젝트 작업 문서·사용자 승인·변경 요청·병합·배포를 조율한다.
Source of Truth
| 영역 | 기준 | 메모 |
|---|---|---|
| Roadmap | Milestone 문서 | 범위, consumer, 완료 evidence 기준 |
| Code | packages/go/streamgate, packages/go/config, apps/edge/internal/openai, apps/edge/internal/service, apps/node/internal/runtime, apps/node/internal/adapters/openai_compat, apps/node/internal/adapters/cli |
transport-agnostic Core와 host codec/rebuilder/dispatcher/release sink의 책임 경계 |
| Contract | openai-compatible-api.md | OpenAI caller surface는 기존 계약을 유지하고 Core는 내부 normalized event만 소비한다 |
| Config Contract | edge-config-runtime-refresh.md | limit 정책의 실제 config field/default/validation/refresh 분류를 구현할 때 함께 갱신한다. 현재 active 계약에 미구현 field를 선반영하지 않는다. |
| User Decision | 현재 사용자 요청 | 기본 500-rune evidence hold, no time-based release, 공통 Core 분리, model/provider별 filter on/off, 동일 immutable evidence batch의 활성 filter 병렬 평가와 all-complete barrier, filter intent를 하나의 공통 RecoveryPlan으로 재조립·재admission하는 Coordinator를 같은 Milestone에 두는 방향이 확정됐다. consumer별 외부 보조 준비는 filter barrier 안이 아니라 plan 선택/current attempt 종료 뒤 optional RecoveryPlanPreparer로 직렬화한다. terminal failure는 bounded sanitized 원인 사슬을 내부에 보존하고 endpoint host가 외부 OpenAI-compatible 오류로 한 번만 평탄화한다. cross-request 오류 사건의 중복 집계와 LLM 기반 소스 분석·수정 제안·승인·변경 요청·병합·배포는 Core 밖의 별도 범용 플랫폼으로 프로젝트화한다. |
State Machine
| 상태 | 진입 조건 | 다음 상태 | 근거 |
|---|---|---|---|
request_bound |
host가 configured byte limit 안의 lossless ingress snapshot, registry/config generation, required filter capability와 recovery budget snapshot을 고정했다 | attempt_dispatching, terminal_error |
같은 request의 recovery cycle이 같은 policy, strategy budget과 request 전체 hard cap을 사용하고 oversize는 dispatch 전 종료 |
attempt_dispatching |
최초 request 또는 rebuilt request가 준비됐다 | filter_resolving, terminal_error |
host AttemptDispatcher가 admission을 한 번 수행해 actual model/provider/execution path/event source/controller를 반환 |
filter_resolving |
attempt의 실제 model/provider/capability가 확정됐다 | normalized, terminal_error |
고정된 registry snapshot으로 active filter set과 hold/failure policy를 attempt-local resolve. replace-attempt는 pending/look-behind를 초기화하고 continuation은 committed look-behind/release cursor를 보존 |
normalized |
codec이 raw provider/agent event를 typed normalized event로 변환했다 | response_staging, evidence_holding, terminal_evaluating, terminal_error |
response-start/text/reasoning/tool/terminal/error event |
response_staging |
provider response-start 또는 protocol opening event가 도착했지만 safe output은 없다 | evidence_holding, terminal_evaluating, terminal_error |
status/header/opening role을 ReleaseSink에 쓰지 않고 attempt-local staging |
evidence_holding |
hold mode가 요구한 rolling rune, terminal 또는 fragment completion evidence가 아직 부족하다 | filter_evaluating, terminal_evaluating, terminal_error |
channel별 rolling/terminal/fragment mode와 hard bound. 시간은 release 조건이 아님 |
filter_evaluating |
subscribed event/hold trigger가 준비되거나 terminal batch가 확정됐다 | arbitrating, terminal_error |
event-applicable/trigger-ready filter는 병렬 실행하고, subscribed event가 없으면 not_applicable_for_epoch, release-blocking trigger 미충족이면 deferred_by_requirement로 표시한다. complete outcome set을 모으는 동안 다음 ingress는 bounded backpressure |
arbitrating |
base event disposition과 모든 active filter outcome이 준비됐다 | releasing, evidence_holding, recovery_planning, terminal_error |
deterministic single action. blocking deferred는 hold, eligible recovery는 candidate를 대체하고 unmatched provider error는 base terminal 유지 |
recovery_planning |
Arbiter가 하나의 RecoveryIntent를 선택했다 |
attempt_aborting, terminal_error |
strategy budget과 request 전체 recovery hard cap, cancel, commit/terminal/tool side effect, strategy eligibility, same-plan re-entry guard를 확인하고 preparer/continuation에 필요한 bounded request-local snapshot과 cursor를 immutable하게 고정한다 |
attempt_aborting |
recovery eligibility가 통과했다 | plan_preparing, rebuilding, terminal_error |
현재 attempt를 idempotent cancel/close하고 실패/timeout이면 보조 plan 준비나 다음 attempt를 시작하지 않음 |
plan_preparing |
이전 attempt ownership이 종료됐고 선택된 plan이 consumer별 보조 directive 준비를 요구한다 | rebuilding, terminal_error |
optional RecoveryPlanPreparer를 plan/idempotency key당 최대 1회, bounded deadline으로 호출한다. filter barrier 밖에서 실행하고 실패/timeout이면 fallback 없이 terminal |
rebuilding |
이전 attempt ownership이 종료됐고 보조 plan 준비가 완료됐거나 필요 없다 | attempt_dispatching, terminal_error |
lossless ingress/continuation snapshot과 endpoint/family RequestRebuilder; cycle당 rebuilt request 하나 |
releasing |
Arbiter가 safe prefix 또는 protocol-safe replacement를 허용했다 | stream_open, evidence_holding, terminal_evaluating |
첫 release라면 staged response-start/opening event를 먼저 한 번 commit하고 safe event를 순서대로 전송 |
stream_open |
downstream transport/opening event 또는 body가 commit됐다 | evidence_holding, terminal_evaluating, recovery_planning, done, terminal_error |
이미 전송한 event rollback/exact/schema replay 금지. safe-prefix continuation 또는 pre-terminal replacement만 capability와 side-effect guard 아래 허용 |
terminal_evaluating |
terminal event가 evidence 충족 전 또는 후 도착했다 | filter_evaluating, terminal_error |
terminal flag가 포함된 final EvidenceBatch, all-filter evaluation |
terminal_error |
provider idle/error, hard buffer overflow, blocking filter failure, recovery ineligible 또는 post-terminal error가 발생했다 | done |
uncommitted이면 staged response를 버리고 host error status/body, stream-open이면 protocol terminal error만 한 번 전송 |
done |
terminal sequence 또는 terminal error가 downstream에 확정됐다 | 없음 | terminal event 1회 |
Interface Contract
- 계약 원문: openai-compatible-api.md
- 입력:
NormalizedEvent: codec이 생성한response_start,text_delta,reasoning_delta,tool_call_fragment,terminal,provider_error중 하나다.response_start는 status와 host가 allowlist로 정규화한 header metadata를 포함한다. hop-by-hop header와 body 변환 시 무효가 되는Content-Length는 staging/release 대상에서 제외한다. Core는 raw wire bytes나 caller 제품명을 받지 않는다.BaseEventDisposition: Core가 event lifecycle에서 만드는hold,release_candidate,terminal_success_candidate,terminal_error_candidate다. response-start는 hold, user-facing safe delta는 release candidate, normal terminal은 success terminal candidate, provider error는 error terminal candidate다. filter가 매칭되지 않아도 이 기본 의미는 사라지지 않는다.StreamTraceContext: runtime이 이미 가진 내부request_id,run_id, provider id, model id correlation이다. Core는 새 public field를 만들지 않고 이 값을 observation에 전파한다.FilterContext: immutable request-local config generation, attempt/epoch, environment, endpoint/family, model group/actual model/provider/execution path, stream, commit/terminal/tool-side-effect state와 trace correlation이다.FilterRegistrySnapshot: request 시작 시 고정한 stable filter id별 구현과 config generation이다. admission 전RequiredFilterCapabilities로 필수 후보를 제한하고 actual target이 정해진 각 attempt에서 environment/model/provider/capability별 active set, priority, hold mode와blocking|observe_onlyenforcement를 resolve한다. config reload는 다음 request부터 반영하고 현재 request의 retry에는 섞지 않는다.EvidenceBatch: 같은 evaluation epoch의 normalized event snapshot, channel별 pending, bounded committed look-behind, staged response-start metadata, terminal 여부와 commit state를 read-only accessor로 제공한다. 모든 trigger-ready filter가 같은 logical snapshot을 읽고 mutable map/slice 참조를 공유하지 않는다. replace-attempt는 buffer를 초기화하고 continuation은 committed look-behind/release cursor만 보존하며 observation/log로 원문을 전달하지 않는다.stream_hold.evidence_runes: 기본 500. environment/model/provider policy가 제공하는 Unicode rune evidence window다.FilterHoldRequirement: 대상 channel,none|rolling_window|terminal_gate|fragment_gate, subscribed normalized event kinds,evidence_runes, terminal/fragment trigger와max_buffer_runeshard bound다.noneevent-only filter는 channel release를 막지 않는다. Core는 channel별 strongest blocking requirement를 합성하고 filter가 별도 raw buffer나 unbounded hold를 만들지 못하게 한다.Filter: Go interface로 stableID, attempt-levelApplies,HoldRequirement/event subscription, synchronousEvaluate(context.Context, FilterContext, EvidenceBatch) (FilterDecision, error)를 제공한다. 공통 context는 caller cancel과 bounded deadline을 전달하며 구현은 취소를 준수해야 한다. Gate Coordinator가 fan-out/수집을 관리하고 구현은 concurrency, request mutation, submit을 소유하지 않는다.FilterDecision:pass,observe,violation,fatal,replacement중 하나다.consumer_id, stablefilter_id,rule_id, sanitized evidence와 선택적RecoveryIntent를 포함한다. evidence 수집 종료점은 static hold requirement가 결정하므로 filter가 평가 뒤 무기한need_more_evidence로 hold를 연장하지 않는다.FilterOutcome: active filter마다evaluated(FilterDecision),evaluation_error,not_applicable_for_epoch,deferred_by_requirement중 하나다. not-applicable은 subscribed event가 없어 release를 막지 않는 outcome이고, deferred는 static blocking trigger 미충족이라 pass로 간주하지 않는다. terminal/hard bound에서 계속 deferred면 invalid/fail policy로 수렴한다.FilterFailurePolicy: Registry가 filter 외부에서 정한다. blocking filter error/deadline은fatal로, observe-only filter error/deadline은observe_erroroutcome으로 정규화되며 둘 다 all-complete 결과와 observation에 포함된다. caller cancel/host shutdown은 barrier를 취소하고 no-release로 종료한다.FailureCause: terminal failure의 내부 원인 한 단계다.stage, 안정code, consumer/filter/rule id만 가지며 raw stack trace, provider body/endpoint, user prompt, output/reasoning 원문, tool args/result, 인증 정보는 가질 수 없다.FailureCauseChain은 request-local 최대 4단계이고 consumer와 Coordinator가 append-only로 조립한다.RecoveryIntent:exact_replay,continuation_repair,schema_repairstrategy id와 typed repair directive, sanitized reason, filter priority를 가진다. raw body/auth, attempt counter, provider 선택 또는 실행 함수는 포함하지 않는다.RecoveryBudget: immutable request-localmax_recovery_attempts_total, strategy별 limit과 사용량을 가진다. 전체 cap 기본값이자 MVP 절대 상한은 최초 provider 실행을 제외한 3회이며 request 시작 시 policy snapshot으로 고정한다. policy는0..3에서 낮출 수만 있고 3 초과는 config load/refresh validation error다. exact/continuation/schema recovery dispatch를 모두 합산하고 각 strategy cap보다 우선한다. 어느 cap이든 소진되면 새 plan을 만들지 않고 terminal로 수렴하며 filter/strategy를 바꿔 우회할 수 없다.IngressSnapshot: endpoint/family rebuilder가 unknown caller field를 손실 없이 보존할 수 있는 request-local snapshot이다.max_ingress_snapshot_bytes기본값이자 MVP 절대 상한은 16 MiB이며 policy는 이보다 낮출 수만 있고 request 시작 시 고정한다. host는io.ReadAll전에http.MaxBytesReader또는 동등한 limit+1 판정을 raw body에 적용한다. OpenAI JSON endpoint는 raw body를 canonical lossless representation으로 사용하고, 다른 codec의 lossless tree는 byte-preserving round-trip fixture가 있을 때만 허용한다.retained_bytes는 runtime heap 추정치가 아니라 request가 소유한 backing byte/string buffer의 보수적 logical byte 합이며 shared backing은 한 번만 센다. SnapshotBuilder는 bounded typed semantic view를 더한 직후, RequestRebuilder는 임시 output 할당 전후의 current peak를 다시 검사하고 canonical full copy를 둘 이상 유지하지 않는다. 따라서 raw body가 pre-read gate와 같아도 typed view를 더한 total retained limit을 넘으면 수용하지 않는다. ingress/rebuild 초과는 snapshot/filter/log/cross-request cache에 원문을 남기거나 provider/recovery budget을 소비하지 않고 terminal로 끝내며 raw auth header도 보존하지 않는다. request 종료/cancel/overflow에서는 retained object를 release한다.AttemptDispatcher: host가 구현하며 rebuilt request와 required capability를 기존 admission에 전달해 actual model/provider/execution path, normalized event source와AttemptController를 가진AttemptBinding하나를 반환한다. concrete provider selection/auth/model rewrite 알고리즘은 Core 밖에 있다.AttemptController: 현재 attempt의 provider transport cancel/close와 ownership release를 idempotent하게 수행한다. Core가 plan 선택 시 고정한 request-local preparation/continuation snapshot과 cursor는 지우지 않는다. recovery는 성공적인 abort 또는 명시적 already-closed 확인 전에 preparer나 다음 dispatch를 호출하지 않는다.RecoveryPlanPreparer: 선택된 plan이 consumer별 bounded directive 준비를 요구할 때 host/consumer가 구현하는 optional interface다. Core가 all-complete arbitration과 eligibility 판정, current attempt ownership 종료 뒤 plan/idempotency key당 최대 1회 bounded deadline으로 호출한다. 입력은 plan 선택 시 고정한 request-local bounded preparation snapshot이며 구체 범위는 consumer 계약이 정하고 observation/log에는 복제하지 않는다. preparer는 provider admission/dispatch, retry/fallback, budget mutation을 소유하지 않고 성공 시 typed directive만 보강한다. 실패는 bounded cause를 가진 terminal로 수렴하며 outbound recovery dispatch가 없으므로 request recovery budget을 소비하지 않는다. snapshot은 성공·실패·cancel 뒤 release한다.RequestRebuilder: host/endpoint/family가 bounded lossless ingress snapshot, selected plan과 continuation snapshot을 새 request로 조립한다. exact replay는 admission rewrite 전 caller raw body를 보존하고 continuation/schema만 typed patch를 적용한다. rebuild 전 예상 peak와 할당 후 실제 peakretained_bytes를 모두 확인하고 한도를 넘으면 기존 snapshot과 임시 output을 즉시 release한 뒤 dispatch하지 않는다.ReleaseSink: host transport가 staged response-start, safe event, replacement, terminal/error를 순서대로 commit하고 현재 commit state를 반환한다. 첫 commit 뒤 status/header를 다시 쓰거나 opening role/event를 중복 전송하지 않는다.
- 출력:
ReleaseEvent: filter 통과 safe prefix 또는 허용된 replacement다.CommitState:transport_uncommitted,stream_open,terminal_committed다. HTTP status/header flush, SSE/agent opening event 또는 첫 body 중 가장 이른 user-visible write가stream_open이다. exact/schema replay는 uncommitted에 한정하고 continuation/replacement는 별도 capability matrix를 따른다.TerminalResult: terminal event 또는 terminal error를 한 번만 전송한다. terminal error는 선택적 boundedFailureCauseChain과 외부 오류의type/code/안전한 message template/paramdescriptor를 가진다. Core는 이를 재해석하지 않고ReleaseSink에 전달하며 endpoint codec이 외부 오류 계약으로 직렬화한다.ArbitrationResult: base disposition과 모든 filter outcome을 합성한release,hold,terminal,recover,replacement단일 action이다. hold는 blocking deferred가 hard bound 전일 때만, recover는 eligible intent가 base candidate를 대체할 때만 선택한다. unmatched error는 terminal을 유지하고 같은 epoch에서 action/dispatch를 둘 이상 만들지 않는다.RecoveryPlan: 선택된 strategy,replace_attempt|continue_streamresume mode, contributing filter/rule ids, plan/idempotency key, strategy attempt와 request 전체 recovery attempt, 각 limit, typed directive, optional bounded preparation snapshot reference와 preparer id/status, commit/side-effect eligibility와 continuation cursor를 포함한다. RecoveryPlan Coordinator만 생성하며 preparer 성공 전에는 rebuild/dispatch 가능한 final plan으로 표시하지 않는다. snapshot 원문은 observation에 직렬화하지 않는다.RebuiltRequest:RequestRebuilder가 immutable ingress snapshot과 plan directive로 만든 endpoint별 새 attempt다. 기존 admission 경로가 provider를 다시 선택하고 auth를 재계산한다.FilterObservation: filter evaluation 또는 lifecycle/coordinator decision마다 emit하는 내부 timeline event다. 기존 request/run/provider/model correlation과 config generation, attempt/epoch, consumer/filter/rule id, hold mode, event kind, effective/pending rune, decision/failure policy, commit state, terminal reason, plan id/strategy/resume mode/shared attempt, preparer id/status/deadline outcome, 최대 4개의 sanitized cause code와 sanitized evidence를 담는다. 기존 sink가 저장·보존을 소유하며 preparer input/output, raw output/prompt/tool args/result/auth와 delta 원문은 담지 않는다.
- 금지:
stream_hold.evidence_runes가 부족한 event를 시간 경과만으로 release하지 않는다.- provider idle/timeout 시 pending tail을 flush하지 않는다.
- Core가 raw protocol parser, caller-specific policy, cross-request cache, LLM judge를 소유하지 않는다.
- 이미 전송한 event를 rollback하거나 exact/schema replay하지 않는다. continuation/replacement도 기존 bytes를 suppress하지 않고 terminal/tool side effect 뒤에는 실행하지 않는다.
- 활성 filter outcome이 모두 수집되기 전에 release/retry/replace하지 않는다. filter error/deadline을 silent pass로 간주하지 않는다.
- 하나의 evaluation epoch를 겹쳐 실행하거나 evaluation 중 provider event를 unbounded queue에 적재하지 않는다.
- provider response status/header 또는 opening role/event를 safe release 전에 eager commit하지 않는다.
- terminal gate hard bound 초과 시 partial output을 release하지 않는다.
- 현재 attempt cancel/close ownership이 끝나기 전에 다음 recovery attempt를 dispatch하지 않는다.
- filter가 request를 직접 mutate하거나 retry loop/counter, provider admission, request submit을 실행하지 않는다.
- 선택된 recovery plan의 후처리인
RecoveryPlanPreparer/translator를 filter evaluation 또는 all-complete barrier 안에서 실행하지 않는다. consumer가 별도 Filter로 선언한 bounded LLM judge까지 금지하는 규칙은 아니지만, 해당 Filter는 자신의 invocation cap/deadline/failure policy를 지켜야 한다. preparer 실패를 재호출/fallback으로 우회하거나 recovery dispatch 없이 budget을 소비하지 않는다. - 같은 evaluation epoch/recovery cycle에서 둘 이상의
RecoveryPlan또는 outbound retry를 실행하지 않는다. 단, request-local strategy budget과 request 전체 hard cap이 모두 남아 있고 새 attempt가 다시 violation을 반환하면 다음 cycle을 시작할 수 있다. max_recovery_attempts_total을 3보다 높이거나 strategy별 limit 합으로 request 전체 cap을 우회하지 않는다.- request body를 unbounded
io.ReadAll한 뒤 길이만 검사하지 않는다.max_ingress_snapshot_bytes를 넘는 요청이나 rebuilt request를 snapshot으로 보존하거나 provider에 dispatch하지 않는다. - OpenAI JSON raw body 외 표현을 byte-preserving 증거 없이 exact replay canonical source로 사용하거나 canonical lossless representation의 full copy를 둘 이상 유지하지 않는다.
FilterObservation에 raw output, raw request/prompt, tool args/result, 인증 정보 또는 caller 제품명을 넣지 않는다.- raw stack trace나 provider endpoint/body를
FailureCauseChain,TerminalResult, public error message,FilterObservation에 넣지 않는다.
Acceptance Scenarios
| ID | Milestone Task | Given | When | Then |
|---|---|---|---|---|
| S01 | evidence-tail |
rolling text delta가 pending에 누적되고 이전 safe prefix가 release됐다 | 기본 500 rune 또는 policy override 200 rune 증거가 충족돼 다음 batch를 만든다 | filter는 같은 크기의 bounded committed look-behind와 pending을 함께 보아 경계 반복을 판정하고 전체 응답을 buffer하지 않는다. replace-attempt는 초기화하고 continuation은 look-behind/cursor를 보존한다 |
| S02 | evidence-tail |
한국어 UTF-8 multi-byte 문자열이 chunk 경계에서 나뉜다 | evidence rune을 계산한다 | byte가 아닌 Unicode rune으로 정확히 계산하고 깨진 문자 없이 release한다 |
| S03 | commit-boundary |
evidence 미충족 pending tail과 staged response-start가 있고 terminal event가 온다 | Core가 최종 all-filter evaluation을 한다 | safe response-start/output 또는 terminal error가 한 번만 결정되고 eager status/role은 없다 |
| S04 | commit-boundary |
evidence 미충족 pending tail 상태에서 provider idle timeout이 난다 | terminal error를 처리한다 | pending tail은 release하지 않고 terminal error만 전송한다 |
| S05 | commit-boundary |
Core가 safe prefix를 전송해 stream이 열렸다 | 이후 provider error 또는 filter violation이 난다 | already released event는 rollback/exact/schema replay하지 않고 continuation-safe가 아니면 terminal policy로 끝낸다 |
| S06 | policy-hook |
output validation, syntax gate, missing-tool gate, integrity filter, workflow terminal hook이 같은 Core를 소비한다 | 각 filter가 자기 detector decision과 선택적 RecoveryIntent를 반환한다 |
semantic policy/intent는 filter별로 분리되고 request mutation/retry loop/rebuild/dispatch는 공통 Coordinator만 수행한다 |
| S07 | policy-hook |
rolling, schema terminal, tool fragment와 provider-error event-only filter가 같은 request에서 활성이다 | Core가 channel requirement와 event applicability outcome을 합성한다 | rolling은 조기 violation 가능, schema는 terminal 전 blocking deferred, tool은 fragment gate, 평상시 provider-error는 not-applicable로 content를 막지 않는다. duplicate buffer/commit은 없다 |
| S08 | filter-observation |
repeat guard와 syntax gate가 stable filter id 및 sanitized fingerprint/count/offset evidence를 제공한다 | Core가 filter와 coordinator lifecycle event를 emit한다 | 같은 request/run/provider/model timeline에서 config generation, attempt/epoch, staging, 각 decision/failure policy, plan/strategy/resume mode를 추적할 수 있고 raw output/prompt/tool args는 없다 |
| S09 | event-contract |
OpenAI와 agent-family codec이 response-start를 포함한 서로 다른 raw event를 같은 normalized table로 변환한다 | transport-agnostic Core와 host ReleaseSink가 event를 처리한다 | Core가 raw parser나 Edge/Node internal type을 import하지 않고 typed event/host interface만으로 같은 lifecycle을 통과한다 |
| S10 | adoption-doc |
output validation, syntax, missing-tool, integrity, workflow consumer 문서가 Core를 참조한다 | consumer boundary와 observation/recovery mapping을 검토한다 | 모든 consumer가 Core link, own semantic policy/intent, stable filter id와 sanitized observation을 명시한다. recovery consumer는 strategy/request-total cap과 common RecoveryPlan을, ingress snapshot consumer는 bounded snapshot을 사용하고 raw buffer·snapshot/rebuild·retry loop를 중복 구현하지 않는다 |
| S11 | filter-registry |
request 중 config reload가 일어나고 recovery admission이 provider를 바꾸며 required filter capability가 있다 | registry가 request snapshot과 각 attempt actual target으로 active set을 resolve한다 | 같은 config generation을 유지하면서 provider별 set은 다시 계산되고 unsupported candidate는 dispatch 전 제외되며 disabled/duplicate filter는 평가되지 않는다 |
| S12 | parallel-evaluation |
ready filter, terminal-trigger filter, error-event-only filter가 있고 평가 중 provider event가 더 도착한다 | Gate Coordinator가 evaluated/deferred/not-applicable outcome과 bounded backpressure를 만든다 | complete outcome set 전 action하지 않고 blocking deferred는 pass가 아니며 event-only not-applicable은 release를 막지 않는다. race/overlap/unbounded queue가 없고 failure/cancel policy가 적용된다 |
| S13 | decision-arbiter |
release/error/terminal base disposition과 pass/not-applicable/deferred/violation/fatal/replacement outcome이 섞여 있다 | Arbiter가 결과를 합성한다 | unmatched provider error는 terminal, matched eligible error는 recovery, blocking deferred는 hold가 되고 priority/stable id로 single action만 선택한다 |
| S14 | recovery-coordinator |
두 filter가 같은 epoch에 retryable violation을 반환하고 strategy budget과 기본/절대 상한 3회의 request 전체 recovery cap이 남아 있다 | Coordinator가 intent를 dedupe하고 commit/side-effect별 eligibility와 두 budget을 판정한다 | cycle마다 plan/idempotency key/dispatch 하나만 만들고 exact/schema는 uncommitted에, continuation은 safe stream-open에만 허용한다. 0/1/3회 policy는 적용되고 4 이상 config는 거부되며 strategy 또는 전체 cap 소진, cancel/side-effect/re-entry 위반은 다른 strategy 우회 없이 terminal로 끝낸다 |
| S15 | request-rebuilder |
configured byte limit 안에서 unknown caller field가 있는 Chat/Responses raw snapshot과 exact/continuation/schema directive가 있다 | endpoint rebuilder와 host dispatcher가 새 attempt를 만든다 | OpenAI JSON raw body를 canonical source로 하나만 보존하고 bounded typed view/rebuild allocation을 계상한다. exact는 admission rewrite 전 caller body와 unknown field를 보존하고 typed patch만 적용한다. concrete model/auth는 admission에서 다시 계산되고 normalized↔tunnel path 전환도 새 AttemptBinding으로 이어진다 |
| S16 | vertical-slice |
model/provider on/off 가능한 NoopFilter와 test-only InjectedViolationFilter가 등록돼 있다 |
staged response-start부터 filters, Arbiter, abort, Rebuilder, Dispatcher, ReleaseSink까지 한 사이클 돈다 | disabled/enabled pass, eager header/role 없음, injected violation one retry, simultaneous violation single retry, provider/path switch, failure policy, backpressure, exhausted terminal이 end-to-end로 검증된다 |
| S17 | commit-boundary |
provider가 response-start를 보낸 뒤 user body release 전에 filter-matched 500 body/error를 보낸다 | provider-error filter가 exact replay intent를 반환한다 | original status/header/role은 commit되지 않고 retry 성공 attempt의 response-start와 output만 한 번 노출된다 |
| S18 | recovery-coordinator |
정상 text prefix가 이미 release된 뒤 repeat filter가 continuation intent를 반환한다 | Coordinator가 tool/terminal side effect가 없는 stream-open eligibility를 확인한다 | released prefix와 committed look-behind/cursor를 보존하고 새 attempt의 opening/prefix 중복 없이 같은 stream에 continuation만 이어 쓰며 exact/schema intent는 거부된다 |
| S19 | evidence-tail |
terminal-gate filter가 활성이고 provider output이 configured hard bound를 넘는다 | Core가 buffer overflow를 감지한다 | partial output이나 staged response-start를 release하지 않고 attempt를 취소한 뒤 terminal error로 끝낸다 |
| S20 | recovery-coordinator |
recovery가 선택됐지만 현재 attempt abort가 timeout 또는 실패한다 | Coordinator가 다음 dispatch를 준비한다 | next attempt를 시작하지 않고 terminal로 끝내 두 live provider attempt와 중복 side effect를 방지한다 |
| S21 | event-contract |
consumer가 repetition_loop_detected 뒤 auxiliary recovery step 실패를 뜻하는 recovery_notice_translation_failed 원인을 terminal로 올린다 |
Core가 terminal result와 host ReleaseSink를 처리한다 | 원인 사슬은 최대 4개의 sanitized code로 보존되고, host는 endpoint별 외부 오류 하나만 전송한다. raw stack/provider body는 observation과 public output에 없다 |
| S22 | request-rebuilder |
ingress raw body가 pre-read limit을 넘거나 raw body가 gate와 같아도 typed view/rebuild 임시 output을 포함한 logical current peak retained size가 기본값/절대 상한 16 MiB 또는 작은 policy override를 넘는다 | host가 limit+1 overflow를 snapshot 생성 전에 판정하고 SnapshotBuilder/Rebuilder가 owned backing은 합산하되 shared backing은 한 번만 세어 typed view 추가 직후와 rebuild 할당 전후에 retained bytes를 다시 검사한다 | pre-read limit-1/limit은 body gate만 통과하고 limit+1은 즉시 거부된다. 이후 initial retained overflow는 endpoint host의 request-too-large 오류, rebuild overflow는 commit state에 맞는 recovery terminal error 하나로 끝난다. canonical raw body와 임시 object를 release하고 request-local cache·filter·로그에 남기지 않으며 provider dispatch/recovery budget 소비가 없고 16 MiB 초과 config는 거부된다 |
| S23 | recovery-coordinator |
all-complete Arbiter가 보조 directive 준비가 필요한 continuation plan 하나를 선택했다 | Coordinator가 bounded preparation/continuation snapshot과 cursor를 고정하고 current provider transport ownership을 종료한 뒤 registered RecoveryPlanPreparer를 실행한다 |
abort가 snapshot을 지우지 않고 preparer는 filter barrier 밖에서 plan/idempotency key당 최대 1회와 bounded deadline을 지킨다. 성공 뒤에만 rebuild/dispatch하고 timeout/invalid/error는 재호출/fallback 없이 single terminal로 끝내며 outbound dispatch가 없으므로 recovery budget을 소비하지 않는다. snapshot은 모든 종료 경로에서 release된다 |
Evidence Map
| Scenario | Required Evidence | agent-task 연결 |
완료 Evidence 기대 |
|---|---|---|---|
| S01 | 200/500-rune policy, pending+look-behind bound, cross-boundary repeat, replace reset/continuation preserve tests | agent-task/m-stream-evidence-gate-core/... |
Roadmap Completion에 evidence-tail, rolling threshold/bounded look-behind/recovery-mode assertion |
| S02 | Korean multi-byte split chunk rune fixture | agent-task/m-stream-evidence-gate-core/... |
Roadmap Completion에 evidence-tail, Unicode boundary assertion |
| S03 | terminal-before-evidence with staged response-start fixture | agent-task/m-stream-evidence-gate-core/... |
Roadmap Completion에 commit-boundary, no eager commit/single terminal assertion |
| S04 | provider idle timeout before evidence fixture | agent-task/m-stream-evidence-gate-core/... |
Roadmap Completion에 commit-boundary, pending-tail no-release assertion |
| S05 | uncommitted/stream-open/terminal-committed error and strategy matrix | agent-task/m-stream-evidence-gate-core/... |
Roadmap Completion에 commit-boundary, no rollback/exact-schema post-open prohibition assertion |
| S06 | OpenAI and agent-family fake codec consumer decision/intent matrix, direct-submit prohibition check | agent-task/m-stream-evidence-gate-core/... |
Roadmap Completion에 policy-hook, consumer semantic/intent ownership and common recovery ownership assertion |
| S07 | rolling/terminal/fragment mixed-mode composition and channel-bound tests | agent-task/m-stream-evidence-gate-core/... |
Roadmap Completion에 policy-hook, strongest bounded requirement/single buffer assertion |
| S08 | multi-consumer observation timeline with config/attempt/epoch/staging/failure/resume mode and raw absence | agent-task/m-stream-evidence-gate-core/... |
Roadmap Completion에 filter-observation, one raw-free request timeline assertion |
| S09 | OpenAI/agent-family fake codec response-start/event table and host-interface package dependency test | agent-task/m-stream-evidence-gate-core/... |
Roadmap Completion에 event-contract, transport-agnostic typed contract/no internal import assertion |
| S10 | five dependent Milestone Core-link/filter-id/sanitized-observation/limits/common-RecoveryPlan mapping review | agent-task/m-stream-evidence-gate-core/... |
Roadmap Completion에 adoption-doc, consumer documentation/no duplicate raw buffer/snapshot/rebuild/retry loop assertion |
| S11 | config-generation isolation, policy precedence, required capability admission, provider-switch and duplicate-id tests | agent-task/m-stream-evidence-gate-core/... |
Roadmap Completion에 filter-registry, snapshot/attempt re-resolution/preflight assertion |
| S12 | evaluated/deferred/not-applicable mixed outcomes, barrier, race, backpressure, failure/cancel fixtures | agent-task/m-stream-evidence-gate-core/... |
Roadmap Completion에 parallel-evaluation, complete outcome set/deferred-vs-event-only/single-flight assertion |
| S13 | base disposition + all outcome arbitration matrix, matched/unmatched error and deterministic repeats | agent-task/m-stream-evidence-gate-core/... |
Roadmap Completion에 decision-arbiter, default terminal preservation/single-action assertion |
| S14 | simultaneous intent, commit-strategy matrix, 0/1/3회 및 4 이상 config validation, bounded cycles, strategy/request-total cap 교차 소진, cancel/side-effect/re-entry fixtures | agent-task/m-stream-evidence-gate-core/... |
Roadmap Completion에 recovery-coordinator, one plan/dispatch per cycle, absolute total-cap 우선과 no strategy bypass assertion |
| S15 | Chat/Responses bounded raw canonical exact/continuation/schema rebuild, byte-preserving alternative codec, unknown field, auth safety and path-switch tests | agent-task/m-stream-evidence-gate-core/... |
Roadmap Completion에 request-rebuilder, caller-body equality/retained-byte accounting/host admission/AttemptBinding assertion |
| S16 | staged-start NoopFilter/InjectedViolationFilter vertical integration with path switch and backpressure |
agent-task/m-stream-evidence-gate-core/... |
Roadmap Completion에 vertical-slice, request-to-recovery-to-release one-cycle assertion |
| S17 | response-start + matched 500 before body, successful replay response exposure fixture | agent-task/m-stream-evidence-gate-core/... |
Roadmap Completion에 commit-boundary, original start hidden/final start once assertion |
| S18 | stream-open repeat continuation, preserved cursor/look-behind and duplicate opening/prefix suppression fixture | agent-task/m-stream-evidence-gate-core/... |
Roadmap Completion에 recovery-coordinator, safe post-open continuation/exact-schema rejection assertion |
| S19 | terminal-gate hard buffer overflow and attempt cancel fixture | agent-task/m-stream-evidence-gate-core/... |
Roadmap Completion에 evidence-tail, fail-closed/no partial release assertion |
| S20 | attempt abort timeout/failure before next dispatch fixture | agent-task/m-stream-evidence-gate-core/... |
Roadmap Completion에 recovery-coordinator, no overlapping live attempt assertion |
| S21 | bounded failure-cause chain, one terminal result, Chat/Responses host error codec fake fixture | agent-task/m-stream-evidence-gate-core/... |
Roadmap Completion에 event-contract, raw-free cause chain/endpoint-specific single-error assertion |
| S22 | host pre-read raw-body limit-1/limit/limit+1, exact-limit body+typed-view overflow, configured smaller limit, rebuild pre/post-allocation peak overflow, initial-vs-rebuild terminal and release/raw-absence fixtures | agent-task/m-stream-evidence-gate-core/... |
Roadmap Completion에 request-rebuilder, body-gate-vs-total-retained 구분/no unbounded read/no dispatch/no budget/no raw retention and 16 MiB absolute-cap assertion |
| S23 | all-complete snapshot freeze, transport-only abort-before-prepare, plan/idempotency-key single call, success/timeout/invalid/error, no-fallback/no-budget/snapshot-release fixtures | agent-task/m-stream-evidence-gate-core/... |
Roadmap Completion에 recovery-coordinator, preparer의 filter-barrier 외부 실행/one-shot deadline/success-only rebuild/request-local snapshot lifetime assertion |
Cross-repo Dependencies
- 없음
Drift Check
- Milestone 기능 Task와 Acceptance Scenario가 일치한다.
- Evidence Map이 code-review/complete.log에서 검증 가능하다.
- agent-contract를 쓰는 경우 SDD에 계약 원문을 복제하지 않았다.
- 사용자 리뷰가 필요한 항목은 없음으로 확인했다.
사용자 리뷰 이력
- 2026-07-23: 사용자 요청으로 evidence-based pending tail을 공통 stream mechanics로 분리했다. 기본 500-rune hold와 time-based release 금지는 확정됐다.
- 2026-07-23: 사용자 요청으로 여러 consumer filter의 decision을 같은 request timeline에서 추적할 수 있는 raw-free
FilterObservation공통 계약을 추가했다. - 2026-07-23: Coordinator를 별도 Milestone으로 분리하지 않고 이 Core Milestone 안의 독립 컴포넌트로 두기로 했다. 활성 filter는 동일 immutable evidence batch를 병렬 평가하고 all-complete barrier 뒤 결과를 합성하며, filter는 model/provider별 on/off 가능한 공통 interface 구현체로 detector와
RecoveryIntent만 제공한다. 공통 Coordinator가 하나의 plan, strategy/request-total budget, endpoint별 request rebuild와 single re-admission을 소유한다. - 2026-07-23: 재검토에서 HTTP/SSE response-start staging, rolling/terminal/fragment hold 분리, single-flight backpressure, request-local config snapshot, strategy별 post-commit eligibility와 host adapter interface를 보강했다. 특히 exact/schema replay는 uncommitted에 한정하고, 반복 continuation은 이미 보낸 safe prefix를 보존한 stream-open 복구로 명시했다.
- 2026-07-23: 안정성 재검토에서 strategy별 budget을 합산하는 최초 실행 제외 기본값/절대 상한 3회의 request 전체 recovery hard cap과 기본값/절대 상한 16 MiB lossless ingress snapshot retained-byte hard limit을 추가했다. OpenAI JSON raw body를 canonical source로 하나만 보존하고 host pre-read limit을 적용하도록 확정했다.
- 2026-07-23: consumer별 번역/보조 호출이 병렬 filter barrier 안에서 반복 실행되지 않도록 optional
RecoveryPlanPreparer를 all-complete/Arbiter와 current attempt ownership 종료 뒤의 one-shot 단계로 추가했다. preparer 실패는 fallback·recovery dispatch·budget 소비 없이 terminal로 수렴한다. - 2026-07-23: cross-request 오류 수집·안전한 지문 기반 중복 집계·연결 소스 분석·수정 제안·프로젝트 작업 문서·사용자 승인·변경 요청·병합·배포·재발 확인은 별도 범용 플랫폼으로 프로젝트화하기로 확정했다. Core는 동기 요청 경로에서 이 루프를 실행하지 않고 raw-free terminal/observation event를 전달하는 경계만 소유한다.
작업 컨텍스트
- 표준선:
packages/go/streamgate는 codec과 semantic filter 사이의 transport-agnostic boundary다. Gate Coordinator가 state/single-flight fan-out/all-complete barrier를, Arbiter가 single action을, RecoveryPlan Coordinator가 strategy eligibility, strategy별 budget, request 전체 hard cap과 abort 뒤 optional one-shotRecoveryPlanPreparer호출 순서를 담당한다. raw parser, concrete admission/auth/model rewrite, preparer 의미와 response writer는 host/consumer interface 뒤에 둔다. - 구현 접점: Edge
writeProviderTunnelResponse의 provider response-start header flush와streamChatCompletion의 opening role write는 현재 eager commit 지점이므로ReleaseSink뒤로 이동한다.streamBufferedChatCompletion/completeChatCompletion의 tool-validation retry loop는 새 Coordinator의 첫 migration 대상이며 기존 loop와 공존시키지 않는다. - 표준선: default rolling hold는 text/reasoning release barrier다. terminal 전체가 필요한 schema/judge는 explicit bounded
terminal_gate, incomplete tool fragment는fragment_gate를 선언한다. rolling look-behind는 사용자 전송을 지연시키지 않고 cross-boundary 판정에만 쓰며 terminal gate를 제외한 기본 경로는 content 전체를 보관하지 않는다. - 표준선: response status/header와 opening role/event도 user-visible commit이다. 첫 safe release 전에는 staging하고, stream-open 이후 exact/schema replay는 금지한다. continuation recovery는 기존 safe prefix와 look-behind/release cursor를 보존하고 terminal/tool side effect 전 같은 downstream stream에 이어 쓰는 별도 resume mode다.
- 표준선: request 시작 시 registry/config generation을 고정한다. recovery가 provider/path를 바꾸면 같은 snapshot으로 attempt-local active set을 다시 resolve하고, required filter capability는 admission 후보 조건으로 전달한다.
- 표준선: Core는 관측 envelope와 request/run/provider/model correlation을 emit하지만 filter 의미나 observation 저장·보존을 소유하지 않는다. filter는 자기 stable filter/rule id와 sanitized evidence만 채우며, 기존 observability sink가 모든 filter/coordinator decision을 하나의
FilterObservationtimeline으로 남긴다. - 표준선: 별도 오류 수정 플랫폼은
오류 발생 → 수집·민감정보 제거 → 안정 지문 기반 사건 생성/중복 count → 실행 버전·커밋·저장소 연결 → 비동기 LLM 분석과 재현 근거 → 중립 수정 제안 → 프로젝트별 작업 문서 → 사용자 승인 → 격리 수정·테스트·독립 검토 → 변경 요청 → 병합·배포 → 재발 관찰생명주기를 소유한다. 같은 오류는 새 작업을 만들지 않고 canonical 사건의 count와 first/last seen·영향 버전만 갱신한다. - 표준선: filter interface는 context-aware synchronous pure evaluation 형태로 유지하고 Gate Coordinator가 goroutine fan-out/수집, single-flight backpressure와 cancel/deadline fail policy를 관리한다. 이로써 filter 구현이 concurrency, request mutation, attempt counter, provider admission을 각각 재구현하지 않는다.
- 표준선: 최초 vertical slice는 production semantic detector 대신 model/provider별 on/off 가능한
NoopFilter와 test-onlyInjectedViolationFilter로 공통 파이프라인 한 사이클을 먼저 검증한다. - 표준선: host는 ingress body를 읽기 전에 기본값/절대 상한 16 MiB의
max_ingress_snapshot_bytes를 적용한다. OpenAI JSON은 raw body를 canonical source로 하나만 보존하고 Core/Rebuilder가 typed view와 rebuild 임시 output까지 retained byte를 계상한다. limit 초과는 provider dispatch와 recovery budget 소비 전에 fail-closed한다. - 표준선: limit policy를 실제 config에 구현할 때 Edge Config And Runtime Refresh Contract에 field/default/range와 refresh 분류를 함께 반영한다. 현재 active contract에는 미구현 config field를 선반영하지 않는다.
- 후속 SDD: OpenAI-compatible 출력 검증 필터 SDD