package openai import ( "encoding/json" "errors" "fmt" "strings" "testing" edgeservice "iop/apps/edge/internal/service" "iop/packages/go/config" "iop/packages/go/streamgate" ) func TestOpenAIRepeatHistoryChatAliases(t *testing.T) { const anchor = "assistant-only anchor" body := []byte(`{ "model":"m", "messages":[ {"role":"assistant","content":"assistant-only anchor"}, {"role":"assistant","reasoning_content":"assistant-only anchor"}, {"role":"assistant","reasoning":"assistant-only anchor"}, {"role":"assistant","reasoning_text":"assistant-only anchor"}, {"role":"assistant","encrypted_reasoning":"RAW_SECRET","signed_reasoning":"RAW_SIGNATURE"} ] }`) history, err := decodeOpenAIChatRepeatHistory(body) if err != nil { t.Fatalf("decodeOpenAIChatRepeatHistory: %v", err) } fingerprint, ok := openAIRepeatTextFingerprint(anchor) if !ok { t.Fatal("anchor fingerprint was empty") } if count, ok := history.assistantCandidate(openAIRepeatChannelContent, fingerprint); !ok || count != 4 { t.Fatalf("content candidate = (%d, %v), want (4, true)", count, ok) } if count, ok := history.assistantCandidate(openAIRepeatChannelReasoning, fingerprint); !ok || count != 4 { t.Fatalf("reasoning candidate = (%d, %v), want (4, true)", count, ok) } for _, sentinel := range []string{"RAW_SECRET", "RAW_SIGNATURE"} { sentinelFingerprint, _ := openAIRepeatTextFingerprint(sentinel) if _, ok := history.assistantCandidate(openAIRepeatChannelReasoning, sentinelFingerprint); ok { t.Fatalf("unknown protected field %q became a reasoning candidate", sentinel) } } userBody := []byte(`{"messages":[ {"role":"user","content":"assistant-only anchor"}, {"role":"assistant","reasoning_content":"assistant-only anchor"}, {"role":"assistant","reasoning":"assistant-only anchor"} ]}`) userHistory, err := decodeOpenAIChatRepeatHistory(userBody) if err != nil { t.Fatalf("decode user-exclusion history: %v", err) } if _, ok := userHistory.assistantCandidate(openAIRepeatChannelReasoning, fingerprint); ok { t.Fatal("user occurrence did not exclude the assistant anchor") } } func TestOpenAIRepeatHistoryAssistantCandidateMetadata(t *testing.T) { const anchor = "assistant\tanchor 한글" fingerprint, ok := openAIRepeatTextFingerprint(anchor) if !ok { t.Fatal("anchor fingerprint was empty") } builder := newOpenAIRepeatHistoryBuilder() builder.recordText("assistant", openAIRepeatChannelContent, anchor) builder.recordText("assistant", openAIRepeatChannelReasoning, "assistant anchor 한글") history := builder.snapshot() for _, channel := range []openAIRepeatChannel{ openAIRepeatChannelContent, openAIRepeatChannelReasoning, } { metadata, found := history.assistantCandidateMetadata(channel, fingerprint) if !found { t.Fatalf("%s candidate metadata is missing", channel) } if metadata.count != 2 || metadata.normalizedRunes != len([]rune("assistant anchor 한글")) { t.Fatalf("%s metadata = %+v", channel, metadata) } } builder.recordText("user", openAIRepeatChannelContent, " assistant anchor\n한글 ") if _, found := builder.snapshot().assistantCandidateMetadata(openAIRepeatChannelContent, fingerprint); found { t.Fatal("normalized user occurrence did not exclude the assistant candidate") } capped := newOpenAIRepeatHistoryBuilder() var firstFingerprint streamgate.FixedFingerprint for i := 0; i < openAIRepeatHistoryMaxFingerprints; i++ { value := fmt.Sprintf("bounded assistant anchor %04d", i) capped.recordText("assistant", openAIRepeatChannelContent, value) capped.recordText("assistant", openAIRepeatChannelContent, value) if i == 0 { firstFingerprint, _ = openAIRepeatTextFingerprint(value) } } capped.recordText("assistant", openAIRepeatChannelContent, "candidate beyond cap") capped.recordText("assistant", openAIRepeatChannelContent, "candidate beyond cap") cappedSnapshot := capped.snapshot() if got := len(cappedSnapshot.assistantCandidates[openAIRepeatChannelContent]); got != openAIRepeatHistoryMaxFingerprints { t.Fatalf("candidate cap = %d, want %d", got, openAIRepeatHistoryMaxFingerprints) } beyondFingerprint, _ := openAIRepeatTextFingerprint("candidate beyond cap") if _, found := cappedSnapshot.assistantCandidateMetadata(openAIRepeatChannelContent, beyondFingerprint); found { t.Fatal("candidate beyond the raw-free fingerprint cap was retained") } // A user occurrence of an already admitted fingerprint must still be // recorded after the cap is full so provenance exclusion cannot be bypassed. capped.recordText("user", openAIRepeatChannelContent, "bounded assistant anchor 0000") if _, found := capped.snapshot().assistantCandidateMetadata(openAIRepeatChannelContent, firstFingerprint); found { t.Fatal("user exclusion was dropped after the fingerprint cap filled") } } func TestOpenAIRepeatHistoryResponsesProvenance(t *testing.T) { body := []byte(`{"model":"m","input":[ {"type":"reasoning","content":[{"type":"reasoning_text","text":"reasoning anchor"},{"type":"encrypted_reasoning","text":"RAW_SECRET"}]}, {"type":"reasoning","content":[{"type":"reasoning_text","text":"reasoning anchor"}]}, {"type":"function_call","call_id":"call-1","name":"lookup","arguments":"{\"id\":1}"}, {"type":"function_call_output","call_id":"call-1","output":"same result"}, {"type":"function_call","call_id":"call-2","name":"lookup","arguments":"{\"id\":1}"}, {"type":"function_call_output","call_id":"call-2","output":"same result"}, {"type":"unknown","payload":"RAW_UNKNOWN"} ]}`) history, err := decodeOpenAIResponsesRepeatHistory(body) if err != nil { t.Fatalf("decodeOpenAIResponsesRepeatHistory: %v", err) } anchorFingerprint, _ := openAIRepeatTextFingerprint("reasoning anchor") if count, ok := history.assistantCandidate(openAIRepeatChannelReasoning, anchorFingerprint); !ok || count != 2 { t.Fatalf("reasoning candidate = (%d, %v), want (2, true)", count, ok) } actionFingerprint, _ := openAIRepeatActionFingerprint("lookup", json.RawMessage(`{"id":1}`)) if count, ok := history.repeatedAction(actionFingerprint); !ok || count != 1 { t.Fatalf("repeated action = (%d, %v), want (1, true)", count, ok) } for _, sentinel := range []string{"RAW_SECRET", "RAW_UNKNOWN"} { fingerprint, _ := openAIRepeatTextFingerprint(sentinel) if _, ok := history.assistantCandidate(openAIRepeatChannelReasoning, fingerprint); ok { t.Fatalf("protected Responses value %q became a candidate", sentinel) } } } func TestOpenAIRepeatHistoryDoesNotInferLineage(t *testing.T) { for name, decode := range map[string]func([]byte) (openAIRepeatHistorySnapshot, error){ "chat": decodeOpenAIChatRepeatHistory, "responses": decodeOpenAIResponsesRepeatHistory, } { t.Run(name, func(t *testing.T) { history, err := decode([]byte(`{"model":"m","metadata":{"session_id":"caller-session"},"messages":[],"input":"current user input"}`)) if err != nil { t.Fatalf("decode history: %v", err) } if len(history.assistantCandidates[openAIRepeatChannelContent]) != 0 || len(history.assistantCandidates[openAIRepeatChannelReasoning]) != 0 || len(history.repeatedActions) != 0 { t.Fatalf("omitted history inferred lineage: %+v", history) } }) } } func TestOpenAIRepeatHistoryProgressBoundary(t *testing.T) { noProgressBody := []byte(`{"messages":[ {"role":"assistant","tool_calls":[{"id":"a","type":"function","function":{"name":"lookup","arguments":"{\"id\":1}"}}]}, {"role":"tool","tool_call_id":"a","content":"same"}, {"role":"assistant","tool_calls":[{"id":"b","type":"function","function":{"name":"lookup","arguments":"{\"id\":1}"}}]}, {"role":"tool","tool_call_id":"b","content":"same"} ]}`) noProgress, err := decodeOpenAIChatRepeatHistory(noProgressBody) if err != nil { t.Fatalf("decode no-progress history: %v", err) } actionFingerprint, _ := openAIRepeatActionFingerprint("lookup", json.RawMessage(`{"id":1}`)) if _, ok := noProgress.repeatedAction(actionFingerprint); !ok { t.Fatal("identical completed action/result was not marked no-progress") } if noProgress.completedProgress() { t.Fatal("identical completed action/result was marked progress") } progressBody := []byte(`{"messages":[ {"role":"assistant","tool_calls":[{"id":"a","type":"function","function":{"name":"lookup","arguments":"{\"id\":1}"}}]}, {"role":"tool","tool_call_id":"a","content":"first"}, {"role":"assistant","tool_calls":[{"id":"b","type":"function","function":{"name":"other","arguments":"{\"id\":2}"}}]}, {"role":"tool","tool_call_id":"b","content":"changed"} ]}`) progress, err := decodeOpenAIChatRepeatHistory(progressBody) if err != nil { t.Fatalf("decode progress history: %v", err) } if !progress.completedProgress() { t.Fatal("changed completed result hash was not marked progress") } if len(progress.repeatedActions) != 0 { t.Fatalf("distinct completed action/result was marked repeated: %+v", progress.repeatedActions) } } func TestOpenAIRepeatHistoryLatestPairWins(t *testing.T) { body := []byte(`{"messages":[ {"role":"assistant","tool_calls":[{"id":"older-a","type":"function","function":{"name":"lookup","arguments":"{\"id\":0}"}}]}, {"role":"tool","tool_call_id":"older-a","content":"older result"}, {"role":"assistant","tool_calls":[{"id":"older-b","type":"function","function":{"name":"lookup","arguments":"{\"id\":1}"}}]}, {"role":"tool","tool_call_id":"older-b","content":"changed result"}, {"role":"assistant","tool_calls":[{"id":"latest-a","type":"function","function":{"name":"lookup","arguments":"{\"id\":1}"}}]}, {"role":"tool","tool_call_id":"latest-a","content":"same latest failure"}, {"role":"assistant","tool_calls":[{"id":"latest-b","type":"function","function":{"name":"lookup","arguments":"{\"id\":1}"}}]}, {"role":"tool","tool_call_id":"latest-b","content":"same latest failure"} ]}`) history, err := decodeOpenAIChatRepeatHistory(body) if err != nil { t.Fatalf("decode history: %v", err) } if history.completedProgress() { t.Fatal("latest identical action/result churn was hidden by older progress") } fingerprint, ok := openAIRepeatActionFingerprint("lookup", json.RawMessage(`{"id":1}`)) if !ok { t.Fatal("build latest action fingerprint") } if _, repeated := history.repeatedAction(fingerprint); !repeated { t.Fatal("latest identical action/result pair is not blockable") } changedLatest := []byte(`{"messages":[ {"role":"assistant","tool_calls":[{"id":"a","type":"function","function":{"name":"lookup","arguments":"{\"id\":1}"}}]}, {"role":"tool","tool_call_id":"a","content":"first"}, {"role":"assistant","tool_calls":[{"id":"b","type":"function","function":{"name":"lookup","arguments":"{\"id\":1}"}}]}, {"role":"tool","tool_call_id":"b","content":"changed"} ]}`) progress, err := decodeOpenAIChatRepeatHistory(changedLatest) if err != nil { t.Fatalf("decode latest progress history: %v", err) } if !progress.completedProgress() { t.Fatal("latest changed result did not release the progress guard") } } func resolvedFilterIDs(resolved []streamgate.ResolvedFilter) map[string]bool { out := make(map[string]bool, len(resolved)) for _, r := range resolved { out[r.FilterID()] = true } return out } // TestOpenAIStreamGateRequiredCapabilityAdmission verifies that a required // output-filter capability excludes candidates that do not advertise it, and // that a request whose only candidates all lack the capability is rejected // before dispatch (S02/S08 pre-admission 400). func TestOpenAIStreamGateRequiredCapabilityAdmission(t *testing.T) { gateCfg := config.StreamEvidenceGateConf{ Enabled: true, Filters: []config.StreamGateFilterPolicyConf{ {Filter: config.StreamGateFilterProviderError, Priority: 20}, }, } fctx := openAIOutputFilterContext{endpoint: openAIRebuildEndpointChat, requestRef: "openai.snap.1"} capable, err := streamgate.NewAttemptTarget("client-model", "ornith:35b", "prov-a", "normalized", []string{"output.provider_error"}) if err != nil { t.Fatalf("NewAttemptTarget(capable): %v", err) } incapable, err := streamgate.NewAttemptTarget("client-model", "ornith:35b", "prov-b", "normalized", nil) if err != nil { t.Fatalf("NewAttemptTarget(incapable): %v", err) } eligible, err := openAIStreamGateAdmitCandidates(gateCfg, fctx, []streamgate.AttemptTarget{capable, incapable}) if err != nil { t.Fatalf("admit(mixed): %v", err) } if len(eligible) != 1 || eligible[0].Provider() != "prov-a" { t.Fatalf("mixed admission = %+v, want only prov-a", eligible) } if _, err := openAIStreamGateAdmitCandidates(gateCfg, fctx, []streamgate.AttemptTarget{incapable}); !errors.Is(err, errStreamGateRequiredCapabilityUnsupported) { t.Fatalf("all-incapable admission err = %v, want errStreamGateRequiredCapabilityUnsupported", err) } // No configured filters => no required capability => every candidate admitted. empty := config.StreamEvidenceGateConf{Enabled: true} admitted, err := openAIStreamGateAdmitCandidates(empty, fctx, []streamgate.AttemptTarget{incapable}) if err != nil { t.Fatalf("admit(no filters): %v", err) } if len(admitted) != 1 { t.Fatalf("no-filter admission = %d, want 1 (unchanged)", len(admitted)) } } // TestOpenAIStreamGatePolicySelectorPrecedence verifies that a provider selector // disabling a filter is re-resolved per attempt target: the same request snapshot // keeps the filter active for one provider and drops it for the disabled provider // (S08 provider switch). func TestOpenAIStreamGatePolicySelectorPrecedence(t *testing.T) { gateCfg := config.StreamEvidenceGateConf{ Enabled: true, Filters: []config.StreamGateFilterPolicyConf{ { Filter: config.StreamGateFilterProviderError, Priority: 20, Selectors: []config.StreamGateFilterSelectorConf{ {Type: config.StreamGateFilterSelectorProvider, Key: "prov-disabled", Enabled: boolPtr(false)}, }, }, }, } fctx := openAIOutputFilterContext{endpoint: openAIRebuildEndpointChat, requestRef: "openai.snap.1"} reqSnap := beginOutputFilterRequest(t, gateCfg, fctx) enabledTarget := attemptTarget(t, "prov-enabled") disabledTarget := attemptTarget(t, "prov-disabled") enabledResolved, err := reqSnap.ResolveAttempt(enabledTarget) if err != nil { t.Fatalf("ResolveAttempt(enabled): %v", err) } if !resolvedFilterIDs(enabledResolved)[openAIProviderErrorFilterID] { t.Errorf("provider_error not active for prov-enabled") } disabledResolved, err := reqSnap.ResolveAttempt(disabledTarget) if err != nil { t.Fatalf("ResolveAttempt(disabled): %v", err) } if resolvedFilterIDs(disabledResolved)[openAIProviderErrorFilterID] { t.Errorf("provider_error must be inactive for prov-disabled selector") } } // TestOpenAIStreamGateCallerNeutralResolution verifies resolution depends only on // protocol/model/provider/path facts, never on a caller product name (S13). // The three fixture labels identify the originating client only to the test; // the byte-identical protocol payload and every request/filter context passed to // production policy resolution deliberately carry no caller-name field. func TestOpenAIStreamGateCallerNeutralResolution(t *testing.T) { gateCfg := outputFilterGateCfg(config.StreamGateFilterEnforcementBlocking) const protocolPayload = `{"model":"qwen","messages":[{"role":"user","content":"hi"}],"metadata":{"scheme":{"type":"object"}},"stream":true}` fixtures := []struct { name string payload []byte }{ {name: "raw HTTP", payload: []byte(protocolPayload)}, {name: "OpenAI SDK", payload: []byte(protocolPayload)}, {name: "Pi", payload: []byte(protocolPayload)}, } var baseline string for _, fixture := range fixtures { t.Run(fixture.name, func(t *testing.T) { if strings.Contains(string(fixture.payload), "caller") || strings.Contains(string(fixture.payload), "agent") { t.Fatalf("fixture unexpectedly contains a caller-name field: %s", fixture.payload) } var req chatCompletionRequest if err := json.Unmarshal(fixture.payload, &req); err != nil { t.Fatalf("decode protocol payload: %v", err) } fctx := openAIOutputFilterContext{ environment: config.StreamGateEnvironmentDev, endpoint: openAIRebuildEndpointChat, modelGroup: req.Model, hasScheme: chatRequestHasSchemeMetadata(req.Metadata), requestRef: "openai.snap.caller-neutral", } reqSnap := beginOutputFilterRequest(t, gateCfg, fctx) target, err := streamgate.NewAttemptTarget(req.Model, "qwen:latest", "prov-a", string(edgeservice.ProviderPoolPathNormalized), []string{ "output.repeat_guard", "output.schema_gate", "output.provider_error", }) if err != nil { t.Fatalf("NewAttemptTarget: %v", err) } resolved, err := reqSnap.ResolveAttempt(target) if err != nil { t.Fatalf("ResolveAttempt: %v", err) } admitted, err := openAIStreamGateAdmitCandidates(gateCfg, fctx, []streamgate.AttemptTarget{target}) if err != nil || len(admitted) != 1 { t.Fatalf("admission decision=(%d,%v), want one admitted target", len(admitted), err) } parts := []string{fmt.Sprintf("path=%s;admitted=%d", target.ExecutionPath(), len(admitted))} for _, filter := range resolved { hold := filter.HoldRequirement() parts = append(parts, fmt.Sprintf("%s:%s:%d:%s:%d", filter.FilterID(), filter.Enforcement(), filter.Priority(), hold.Mode(), hold.EvidenceRunes())) } signature := strings.Join(parts, "|") if baseline == "" { baseline = signature } else if signature != baseline { t.Fatalf("caller-neutral path/threshold/decision changed:\n got %s\nwant %s", signature, baseline) } }) } } // TestOpenAIStreamGateConfigReloadIsolation verifies each request resolves against // the generation snapshot it began with: an enabled-filter generation keeps the // filter, a disabled-filter generation drops it, and a request context cannot // begin against a mismatched generation snapshot (S08 config reload isolation). func TestOpenAIStreamGateConfigReloadIsolation(t *testing.T) { enabledFilter, err := newOpenAIOutputFilter(openAIOutputFilterProviderError, 500, 20, "openai.snap.1") if err != nil { t.Fatalf("newOpenAIOutputFilter: %v", err) } enabledReg, err := streamgate.NewFilterRegistration(enabledFilter, "output.provider_error", true, streamgate.FilterEnforcementBlocking, streamGateFilterTimeout, 20) if err != nil { t.Fatalf("NewFilterRegistration: %v", err) } snapGen1, err := streamgate.NewFilterRegistrySnapshot("edge.gen.1", []streamgate.FilterRegistration{enabledReg}, nil) if err != nil { t.Fatalf("snapshot gen1: %v", err) } snapGen2, err := streamgate.NewFilterRegistrySnapshot("edge.gen.2", nil, nil) if err != nil { t.Fatalf("snapshot gen2: %v", err) } reqGen1 := beginGenerationRequest(t, snapGen1, "edge.gen.1") reqGen2 := beginGenerationRequest(t, snapGen2, "edge.gen.2") target := attemptTarget(t, "prov-a") gen1Resolved, err := reqGen1.ResolveAttempt(target) if err != nil { t.Fatalf("ResolveAttempt(gen1): %v", err) } if !resolvedFilterIDs(gen1Resolved)[openAIProviderErrorFilterID] { t.Errorf("gen1 request must keep its enabled provider_error filter") } gen2Resolved, err := reqGen2.ResolveAttempt(target) if err != nil { t.Fatalf("ResolveAttempt(gen2): %v", err) } if len(gen2Resolved) != 0 { t.Errorf("gen2 request resolved %d filters, want 0 (its own generation)", len(gen2Resolved)) } // A request context cannot begin against a mismatched generation snapshot. mismatchCtx, err := streamgate.NewRequestFilterContext( "edge.gen.2", "attempt.x", streamGateEnvironment, openAIRebuildEndpointChat, openAIRebuildFamily, "", streamgate.CommitStateTransportUncommitted, false, false, "", ) if err != nil { t.Fatalf("NewRequestFilterContext(mismatch): %v", err) } if _, err := snapGen1.BeginRequest(mismatchCtx); err == nil { t.Errorf("BeginRequest with a mismatched generation succeeded, want generation isolation error") } } func TestOpenAIStreamGatePolicyTargetMatrix(t *testing.T) { gateCfg := config.StreamEvidenceGateConf{ Environment: config.StreamGateEnvironmentDevCorp, Filters: []config.StreamGateFilterPolicyConf{{ Filter: config.StreamGateFilterProviderError, Enabled: boolPtr(false), Selectors: []config.StreamGateFilterSelectorConf{ {Type: config.StreamGateFilterSelectorEnvironment, Key: config.StreamGateEnvironmentDevCorp, Enabled: boolPtr(true)}, {Type: config.StreamGateFilterSelectorModelGroup, Key: "gemma", Enabled: boolPtr(false)}, {Type: config.StreamGateFilterSelectorModel, Key: "ornith:35b", Enabled: boolPtr(true)}, {Type: config.StreamGateFilterSelectorProvider, Key: "prov-off", Enabled: boolPtr(false)}, }, }}, } fctx := openAIOutputFilterContext{ environment: config.StreamGateEnvironmentDevCorp, endpoint: openAIRebuildEndpointChat, modelGroup: "qwen", requestRef: "openai.snap.matrix", } reqSnap := beginOutputFilterRequest(t, gateCfg, fctx) tests := []struct { name, group, model, provider string wantActive bool }{ {name: "environment enables base-disabled", group: "qwen", model: "generic", provider: "prov-a", wantActive: true}, {name: "model-group disables environment", group: "gemma", model: "generic", provider: "prov-a", wantActive: false}, {name: "model overrides model-group", group: "gemma", model: "ornith:35b", provider: "prov-a", wantActive: true}, {name: "provider overrides model", group: "gemma", model: "ornith:35b", provider: "prov-off", wantActive: false}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { target, err := streamgate.NewAttemptTarget(tc.group, tc.model, tc.provider, string(edgeservice.ProviderPoolPathNormalized), nil) if err != nil { t.Fatalf("NewAttemptTarget: %v", err) } resolved, err := reqSnap.ResolveAttempt(target) if err != nil { t.Fatalf("ResolveAttempt: %v", err) } active := resolvedFilterIDs(resolved)[openAIProviderErrorFilterID] if active != tc.wantActive { t.Fatalf("active=%t, want %t", active, tc.wantActive) } }) } } func TestOpenAIStreamGateObserveOnlyDoesNotGateAdmission(t *testing.T) { gateCfg := config.StreamEvidenceGateConf{ Environment: config.StreamGateEnvironmentDev, Filters: []config.StreamGateFilterPolicyConf{{ Filter: config.StreamGateFilterProviderError, Enforcement: config.StreamGateFilterEnforcementObserveOnly, Selectors: []config.StreamGateFilterSelectorConf{{ Type: config.StreamGateFilterSelectorProvider, Key: "prov-block", Enforcement: config.StreamGateFilterEnforcementBlocking, }}, }}, } fctx := openAIOutputFilterContext{environment: config.StreamGateEnvironmentDev, endpoint: openAIRebuildEndpointChat, modelGroup: "qwen", requestRef: "openai.snap.observe"} observeTarget, _ := streamgate.NewAttemptTarget("qwen", "qwen:latest", "prov-observe", "normalized", nil) required, err := openAIStreamGateRequiredCapabilities(gateCfg, fctx, observeTarget) if err != nil { t.Fatalf("RequiredCapabilities(observe): %v", err) } if len(required) != 0 { t.Fatalf("observe-only required capabilities=%v, want none", required) } if admitted, err := openAIStreamGateAdmitCandidates(gateCfg, fctx, []streamgate.AttemptTarget{observeTarget}); err != nil || len(admitted) != 1 { t.Fatalf("observe-only admission=(%d,%v), want admitted", len(admitted), err) } blockingTarget, _ := streamgate.NewAttemptTarget("qwen", "qwen:latest", "prov-block", "normalized", nil) if _, err := openAIStreamGateAdmitCandidates(gateCfg, fctx, []streamgate.AttemptTarget{blockingTarget}); !errors.Is(err, errStreamGateRequiredCapabilityUnsupported) { t.Fatalf("blocking selector err=%v, want capability rejection", err) } } func beginOutputFilterRequest(t *testing.T, gateCfg config.StreamEvidenceGateConf, fctx openAIOutputFilterContext) streamgate.RequestFilterSnapshot { t.Helper() regs, policies, err := openAIOutputFilterRegistrations(gateCfg, fctx) if err != nil { t.Fatalf("openAIOutputFilterRegistrations: %v", err) } snap, err := streamgate.NewFilterRegistrySnapshot(streamGateConfigGeneration, regs, policies) if err != nil { t.Fatalf("NewFilterRegistrySnapshot: %v", err) } reqCtx, err := openAIOutputFilterRequestContext(fctx) if err != nil { t.Fatalf("openAIOutputFilterRequestContext: %v", err) } reqSnap, err := snap.BeginRequest(reqCtx) if err != nil { t.Fatalf("BeginRequest: %v", err) } return reqSnap } func beginGenerationRequest(t *testing.T, snap streamgate.FilterRegistrySnapshot, generation string) streamgate.RequestFilterSnapshot { t.Helper() reqCtx, err := streamgate.NewRequestFilterContext( generation, "attempt.1", streamGateEnvironment, openAIRebuildEndpointChat, openAIRebuildFamily, "", streamgate.CommitStateTransportUncommitted, false, false, "", ) if err != nil { t.Fatalf("NewRequestFilterContext: %v", err) } reqSnap, err := snap.BeginRequest(reqCtx) if err != nil { t.Fatalf("BeginRequest: %v", err) } return reqSnap } func attemptTarget(t *testing.T, provider string) streamgate.AttemptTarget { t.Helper() target, err := streamgate.NewAttemptTarget("client-model", "ornith:35b", provider, "normalized", []string{"output.repeat_guard", "output.schema_gate", "output.provider_error"}) if err != nil { t.Fatalf("NewAttemptTarget(%s): %v", provider, err) } return target }