package streamgate import ( "fmt" "strings" "testing" "time" ) // now is a deterministic timestamp shared across tests. var now = time.Date(2026, 7, 24, 12, 0, 0, 0, time.UTC) // --------------------------------------------------------------------------- // API-1: FilterHoldRequirement, EvidencePlan, EvidenceTail core state // --------------------------------------------------------------------------- // TestFilterHoldRequirementValidatesModeMatrix verifies the field matrix for // every FilterHoldMode. Each mode has required, forbidden, and optional // fields, plus hard rune bounds. The test covers the constructor surface and // the Validate path. func TestFilterHoldRequirementValidatesModeMatrix(t *testing.T) { textKinds := []EventKind{EventKindTextDelta, EventKindReasoningDelta} toolKinds := []EventKind{EventKindToolCallFragment} allKinds := []EventKind{EventKindTextDelta, EventKindReasoningDelta, EventKindToolCallFragment} // --- none mode ----------------------------------------------------------- t.Run("none", func(t *testing.T) { // Valid: channel + subscribed kinds, zeroed rune/trigger/buffer. r, err := NewFilterHoldRequirementNone("ch-none", textKinds) if err != nil { t.Fatalf("unexpected error: %v", err) } if err := r.Validate(); err != nil { t.Fatalf("validate failed: %v", err) } if r.EvidenceRunes() != 0 { t.Errorf("evidenceRunes want 0, got %d", r.EvidenceRunes()) } if r.TriggerKind() != "" { t.Errorf("trigger want empty, got %q", r.TriggerKind()) } if r.MaxBufferRunes() != 0 { t.Errorf("maxBuffer want 0, got %d", r.MaxBufferRunes()) } if r.IsBlocking() { t.Errorf("none must not be blocking") } // Empty channel rejected. _, err = NewFilterHoldRequirementNone("", textKinds) if err == nil { t.Error("empty channel should be rejected") } // Empty subscribed kinds rejected. _, err = NewFilterHoldRequirementNone("ch", nil) if err == nil { t.Error("nil kinds should be rejected") } }) // --- rolling_window mode ------------------------------------------------- t.Run("rolling_window", func(t *testing.T) { // Valid defaults. r, err := NewFilterHoldRequirementRolling("ch-roll", textKinds, 500) if err != nil { t.Fatalf("unexpected error: %v", err) } if err := r.Validate(); err != nil { t.Fatalf("validate failed: %v", err) } if r.EvidenceRunes() != 500 { t.Errorf("evidenceRunes want 500, got %d", r.EvidenceRunes()) } if r.TriggerKind() != "" { t.Errorf("trigger must be empty for rolling, got %q", r.TriggerKind()) } if r.MaxBufferRunes() == 0 { t.Error("rolling must have a positive max_buffer_runes") } if !r.IsBlocking() { t.Error("rolling must be blocking") } // Below minimum evidence runes. _, err = NewFilterHoldRequirementRolling("ch", textKinds, 0) if err == nil { t.Error("evidenceRunes=0 should be rejected") } // Above maximum evidence runes. _, err = NewFilterHoldRequirementRolling("ch", textKinds, maxEvidenceRunes+1) if err == nil { t.Error("evidenceRunes>max should be rejected") } // Trigger kind is forbidden on rolling. bad := FilterHoldRequirement{ channel: "ch", mode: FilterHoldModeRolling, subscribedKinds: textKinds, evidenceRunes: 100, triggerKind: EventKindTerminal, maxBufferRunes: defaultMaxBufferRunes, } if err := bad.Validate(); err == nil { t.Error("rolling with trigger should be rejected") } // maxBufferRunes below minimum. bad2 := FilterHoldRequirement{ channel: "ch", mode: FilterHoldModeRolling, subscribedKinds: textKinds, evidenceRunes: 100, maxBufferRunes: minMaxBufferRunes - 1, } if err := bad2.Validate(); err == nil { t.Error("maxBuffer below minimum should be rejected") } }) // --- terminal_gate mode -------------------------------------------------- t.Run("terminal_gate", func(t *testing.T) { r, err := NewFilterHoldRequirementTerminalGate("ch-term", allKinds, EventKindTerminal) if err != nil { t.Fatalf("unexpected error: %v", err) } if err := r.Validate(); err != nil { t.Fatalf("validate failed: %v", err) } if r.TriggerKind() != EventKindTerminal { t.Errorf("trigger want terminal, got %q", r.TriggerKind()) } if r.EvidenceRunes() != 0 { t.Error("terminal_gate must not have evidenceRunes") } if !r.IsBlocking() { t.Error("terminal_gate must be blocking") } // Provider error also accepted as trigger. r2, err := NewFilterHoldRequirementTerminalGate("ch", allKinds, EventKindProviderError) if err != nil { t.Fatalf("provider_error trigger should be accepted: %v", err) } if err := r2.Validate(); err != nil { t.Fatalf("validate: %v", err) } // Non-terminal kind rejected. _, err = NewFilterHoldRequirementTerminalGate("ch", allKinds, EventKindTextDelta) if err == nil { t.Error("text_delta trigger should be rejected for terminal_gate") } // evidenceRunes is forbidden. bad := FilterHoldRequirement{ channel: "ch", mode: FilterHoldModeTerminalGate, subscribedKinds: allKinds, evidenceRunes: 100, triggerKind: EventKindTerminal, maxBufferRunes: defaultMaxBufferRunes, } if err := bad.Validate(); err == nil { t.Error("terminal_gate with evidenceRunes should be rejected") } }) // --- fragment_gate mode -------------------------------------------------- t.Run("fragment_gate", func(t *testing.T) { r, err := NewFilterHoldRequirementFragmentGate("ch-frag", toolKinds, EventKindToolCallFragment) if err != nil { t.Fatalf("unexpected error: %v", err) } if err := r.Validate(); err != nil { t.Fatalf("validate failed: %v", err) } if r.TriggerKind() != EventKindToolCallFragment { t.Errorf("trigger want tool_call_fragment, got %q", r.TriggerKind()) } if r.EvidenceRunes() != 0 { t.Error("fragment_gate must not have evidenceRunes") } if !r.IsBlocking() { t.Error("fragment_gate must be blocking") } // Wrong trigger kind. _, err = NewFilterHoldRequirementFragmentGate("ch", toolKinds, EventKindTextDelta) if err == nil { t.Error("text_delta trigger should be rejected for fragment_gate") } // evidenceRunes is forbidden. bad := FilterHoldRequirement{ channel: "ch", mode: FilterHoldModeFragmentGate, subscribedKinds: toolKinds, evidenceRunes: 10, triggerKind: EventKindToolCallFragment, maxBufferRunes: defaultMaxBufferRunes, } if err := bad.Validate(); err == nil { t.Error("fragment_gate with evidenceRunes should be rejected") } }) // --- duplicate subscribed kinds rejected --------------------------------- t.Run("duplicateKinds", func(t *testing.T) { dupKinds := []EventKind{EventKindTextDelta, EventKindTextDelta} _, err := NewFilterHoldRequirementRolling("ch", dupKinds, 100) if err == nil { t.Error("duplicate kinds should be rejected") } }) // --- unknown mode rejected ----------------------------------------------- t.Run("unknownMode", func(t *testing.T) { bad := FilterHoldRequirement{ channel: "ch", mode: "not_a_mode", subscribedKinds: textKinds, } if err := bad.Validate(); err == nil { t.Error("unknown mode should be rejected") } }) } // TestEvidencePlanComposesOnlyBlockingRequirements verifies that observe-only // and none-mode bindings never create a channel hold, while blocking bindings // are kept and the strongest mode per channel wins. func TestEvidencePlanComposesOnlyBlockingRequirements(t *testing.T) { textKinds := []EventKind{EventKindTextDelta, EventKindReasoningDelta} toolKinds := []EventKind{EventKindToolCallFragment} t.Run("observeOnlyDoesNotBlock", func(t *testing.T) { // Build a blocking rolling requirement. roll, err := NewFilterHoldRequirementRolling("ch", textKinds, 500) if err != nil { t.Fatalf("build rolling: %v", err) } // Observe-only binding for the same channel. bindObs, err := NewFilterHoldBinding("f-obs", roll, false) if err != nil { t.Fatalf("build observe binding: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{bindObs}) if err != nil { t.Fatalf("NewEvidencePlan: %v", err) } if plan.HasBlockingRequirement("ch") { t.Error("observe-only must not create blocking requirement") } if plan.SubscribeKinds("ch") == nil { t.Error("subscribe kinds should still include observe kinds") } }) t.Run("noneModeDoesNotBlock", func(t *testing.T) { none, err := NewFilterHoldRequirementNone("ch", textKinds) if err != nil { t.Fatalf("build none: %v", err) } bind, err := NewFilterHoldBinding("f-none", none, true) if err != nil { t.Fatalf("build binding: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{bind}) if err != nil { t.Fatalf("NewEvidencePlan: %v", err) } // None mode IsBlocking=false, so strongest per channel will skip it. if plan.HasBlockingRequirement("ch") { t.Error("none mode must not create blocking requirement") } }) t.Run("strongestWins", func(t *testing.T) { // Same channel: rolling + fragment_gate. Fragment is stronger and // rolling has no trigger so compatible. roll, err := NewFilterHoldRequirementRolling("ch", textKinds, 500) if err != nil { t.Fatalf("build rolling: %v", err) } frag, err := NewFilterHoldRequirementFragmentGate("ch", toolKinds, EventKindToolCallFragment) if err != nil { t.Fatalf("build fragment: %v", err) } b1, err := NewFilterHoldBinding("f1", roll, true) if err != nil { t.Fatalf("build binding 1: %v", err) } b2, err := NewFilterHoldBinding("f2", frag, true) if err != nil { t.Fatalf("build binding 2: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{b1, b2}) if err != nil { t.Fatalf("NewEvidencePlan: %v", err) } if !plan.HasBlockingRequirement("ch") { t.Fatal("expected blocking requirement") } req, err := plan.BlockingRequirement("ch") if err != nil { t.Fatalf("blocking requirement: %v", err) } if req.Mode() != FilterHoldModeFragmentGate { t.Errorf("strongest mode want fragment_gate, got %s", req.Mode()) } // Blocking kinds should be the union of both. blockingKinds := plan.BlockingKinds("ch") if len(blockingKinds) != 3 { t.Errorf("blocking kinds want 3 (textDelta, reasoningDelta, toolCallFragment), got %d", len(blockingKinds)) } // Observe-only kinds should be only the rolling kinds. observeKinds := plan.ObserveKinds("ch") if len(observeKinds) != 2 { t.Errorf("observe kinds want 2 (textDelta, reasoningDelta), got %d", len(observeKinds)) } }) t.Run("incompatibleTriggersRejected", func(t *testing.T) { // fragment_gate (trigger=tool_call_fragment) + terminal_gate // (trigger=terminal) on same channel: triggers differ → reject. frag, err := NewFilterHoldRequirementFragmentGate("ch", toolKinds, EventKindToolCallFragment) if err != nil { t.Fatalf("build fragment: %v", err) } term, err := NewFilterHoldRequirementTerminalGate("ch", textKinds, EventKindTerminal) if err != nil { t.Fatalf("build terminal: %v", err) } b1, err := NewFilterHoldBinding("f1", frag, true) if err != nil { t.Fatalf("build binding 1: %v", err) } b2, err := NewFilterHoldBinding("f2", term, true) if err != nil { t.Fatalf("build binding 2: %v", err) } _, err = NewEvidencePlan([]FilterHoldBinding{b1, b2}) if err == nil { t.Error("incompatible terminal triggers should be rejected") } }) t.Run("emptyBindings", func(t *testing.T) { plan, err := NewEvidencePlan(nil) if err != nil { t.Fatalf("empty bindings: %v", err) } if plan.HasBlockingRequirement("anything") { t.Error("empty plan must not have blocking requirements") } }) } // TestEvidenceTailRollingRuneThresholdAndLookBehind verifies rolling threshold // behaviour at the default 500-rune and a policy-override 200-rune window, // plus the bounded cross-boundary look-behind invariant. func TestEvidenceTailRollingRuneThresholdAndLookBehind(t *testing.T) { textKinds := []EventKind{EventKindTextDelta} t.Run("default500RuneThreshold", func(t *testing.T) { req, err := NewFilterHoldRequirementRolling("ch", textKinds, 500) if err != nil { t.Fatalf("build requirement: %v", err) } bind, err := NewFilterHoldBinding("f", req, true) if err != nil { t.Fatalf("build binding: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{bind}) if err != nil { t.Fatalf("build plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("build tail: %v", err) } // Feed 499 'a' chars — should not trigger. ev, err := NewTextDeltaEvent("ch", repeatRune('a', 499), now) if err != nil { t.Fatalf("build event: %v", err) } epoch, signal, err := tail.Append(ev) if err != nil { t.Fatalf("append: %v", err) } if signal != EvidenceTailSignalNone { t.Errorf("signal want none before threshold, got %s", signal) } if epoch.Triggered() { t.Error("epoch must not be triggered below threshold") } if cs := tail.channelState["ch"]; cs == nil { t.Fatal("channel state should exist") } else if cs.pendingRunes != 499 { t.Errorf("pendingRunes want 499, got %d", cs.pendingRunes) } // 500th rune triggers the threshold. ev2, err := NewTextDeltaEvent("ch", "a", now) if err != nil { t.Fatalf("build event: %v", err) } _, signal, err = tail.Append(ev2) if err != nil { t.Fatalf("append 500th: %v", err) } if signal != EvidenceTailSignalThreshold { t.Errorf("signal want threshold at 500, got %s", signal) } }) t.Run("policy200RuneThreshold", func(t *testing.T) { req, err := NewFilterHoldRequirementRolling("ch", textKinds, 200) if err != nil { t.Fatalf("build requirement: %v", err) } bind, err := NewFilterHoldBinding("f", req, true) if err != nil { t.Fatalf("build binding: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{bind}) if err != nil { t.Fatalf("build plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("build tail: %v", err) } // Feed 200 runes in chunks — threshold fires on the 200th. var total int signal := EvidenceTailSignalNone for i := 0; i < 25; i++ { chunk := repeatRune('한', 8) // 8 korean chars = 24 runes ev, err := NewTextDeltaEvent("ch", chunk, now) if err != nil { t.Fatalf("build event: %v", err) } _, signal, err = tail.Append(ev) if err != nil { t.Fatalf("append: %v", err) } total += 24 if signal == EvidenceTailSignalThreshold { break } } if signal != EvidenceTailSignalThreshold { t.Errorf("signal want threshold, got %s", signal) } }) t.Run("boundedLookBehind", func(t *testing.T) { req, err := NewFilterHoldRequirementRolling("ch", textKinds, 10) if err != nil { t.Fatalf("build requirement: %v", err) } bind, err := NewFilterHoldBinding("f", req, true) if err != nil { t.Fatalf("build binding: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{bind}) if err != nil { t.Fatalf("build plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("build tail: %v", err) } // Fill to threshold. for i := 0; i < 5; i++ { ev, err := NewTextDeltaEvent("ch", repeatRune('b', 2), now) if err != nil { t.Fatalf("build event: %v", err) } _, _, err = tail.Append(ev) if err != nil { t.Fatalf("append: %v", err) } } cs := tail.channelState["ch"] if len(cs.pendingEntries) != 5 { t.Errorf("pending want 5, got %d", len(cs.pendingEntries)) } if cs.pendingRunes != 10 { t.Errorf("pendingRunes want 10, got %d", cs.pendingRunes) } }) } // TestEvidenceTailPreservesKoreanRuneChunks verifies that Korean multi-byte // UTF-8 strings are counted by Unicode runes (not bytes) and that invalid // UTF-8 is rejected without mutating state. func TestEvidenceTailPreservesKoreanRuneChunks(t *testing.T) { allKinds := []EventKind{EventKindTextDelta, EventKindReasoningDelta} req, err := NewFilterHoldRequirementRolling("ch", allKinds, 3) if err != nil { t.Fatalf("build requirement: %v", err) } bind, err := NewFilterHoldBinding("f", req, true) if err != nil { t.Fatalf("build binding: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{bind}) if err != nil { t.Fatalf("build plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("build tail: %v", err) } t.Run("koreanChunksCountedAsRunes", func(t *testing.T) { // "한글" = 2 runes, each 3 bytes in UTF-8. ev, err := NewTextDeltaEvent("ch", "한글", now) if err != nil { t.Fatalf("build event: %v", err) } _, _, err = tail.Append(ev) if err != nil { t.Fatalf("append: %v", err) } cs := tail.channelState["ch"] if cs.pendingRunes != 2 { t.Errorf("pendingRunes want 2 for '한글', got %d", cs.pendingRunes) } // One more Korean char = 3 runes total → threshold. ev2, err := NewTextDeltaEvent("ch", "한", now) if err != nil { t.Fatalf("build event: %v", err) } _, signal, err := tail.Append(ev2) if err != nil { t.Fatalf("append: %v", err) } if signal != EvidenceTailSignalThreshold { t.Errorf("signal want threshold at 3 runes, got %s", signal) } }) t.Run("koreanSplitAcrossChunks", func(t *testing.T) { tail2, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("build tail: %v", err) } // Feed partial Korean characters — each Append validates UTF-8. // Split "한" (UTF-8: E1 8A 90) into two pieces. han := []byte{0xE1, 0x8A, 0x90} partial := string(han[:2]) // Invalid UTF-8 prefix. // Valid chunk first. ev, err := NewTextDeltaEvent("ch", "가", now) // 1 rune, 3 bytes valid UTF-8. if err != nil { t.Fatalf("build event: %v", err) } _, _, err = tail2.Append(ev) if err != nil { t.Fatalf("append valid: %v", err) } // Invalid UTF-8 chunk must be rejected. evBad, err := NewTextDeltaEvent("ch", partial, now) if err != nil { t.Fatalf("build invalid event: %v", err) } _, _, err = tail2.Append(evBad) if err == nil { t.Error("invalid UTF-8 must be rejected with error") } // State must not have been mutated by the invalid append. cs := tail2.channelState["ch"] if cs.pendingRunes != 1 { t.Errorf("pendingRunes must stay 1 after invalid append, got %d", cs.pendingRunes) } }) t.Run("reasoningDeltaAlsoCounted", func(t *testing.T) { tail3, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("build tail: %v", err) } ev, err := NewReasoningDeltaEvent("ch", "한글", now) if err != nil { t.Fatalf("build event: %v", err) } _, _, err = tail3.Append(ev) if err != nil { t.Fatalf("append: %v", err) } cs := tail3.channelState["ch"] if cs.pendingRunes != 2 { t.Errorf("reasoning rune count want 2, got %d", cs.pendingRunes) } }) } // TestEvidenceTailKeysFragmentsByToolCall verifies that fragment completion for // one tool-call ID does not accidentally trigger completion for another, and // that interleaved fragments on the same channel are tracked independently. // Fragment state is now accumulated automatically by Append. func TestEvidenceTailKeysFragmentsByToolCall(t *testing.T) { toolKinds := []EventKind{EventKindToolCallFragment} req, err := NewFilterHoldRequirementFragmentGate("ch", toolKinds, EventKindToolCallFragment) if err != nil { t.Fatalf("build requirement: %v", err) } bind, err := NewFilterHoldBinding("f", req, true) if err != nil { t.Fatalf("build binding: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{bind}) if err != nil { t.Fatalf("build plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("build tail: %v", err) } t.Run("interleavedToolIDsIndependent", func(t *testing.T) { // Tool A and Tool B fragments interleaved. Append automatically // registers fragment state. evA1, err := NewToolCallFragmentEvent("ch", "call-A", "funcA", `{"x":1}`, now) if err != nil { t.Fatalf("build event A1: %v", err) } _, _, err = tail.Append(evA1) if err != nil { t.Fatalf("append A1: %v", err) } evB1, err := NewToolCallFragmentEvent("ch", "call-B", "funcB", `{"y":2}`, now) if err != nil { t.Fatalf("build event B1: %v", err) } _, _, err = tail.Append(evB1) if err != nil { t.Fatalf("append B1: %v", err) } // Complete A — should mark A as completed. epoch, signal, ok, err := tail.CompleteFragment("ch", "call-A") if err != nil { t.Fatalf("complete A: %v", err) } if !ok { t.Error("complete A should return true") } if signal == EvidenceTailSignalNone { t.Error("complete A should produce a trigger signal") } if epoch.ID() == 0 { t.Error("complete A should return an epoch") } // Complete B — independent of A. epochB, signalB, okB, err := tail.CompleteFragment("ch", "call-B") if err != nil { t.Fatalf("complete B: %v", err) } if !okB { t.Error("complete B should return true") } if signalB == EvidenceTailSignalNone { t.Error("complete B should produce a trigger signal") } if epochB.ID() == 0 { t.Error("complete B should return an epoch") } // Re-complete A (already completed) — should return false. _, _, ok2, err := tail.CompleteFragment("ch", "call-A") if err != nil { t.Fatalf("complete A again: %v", err) } if ok2 { t.Error("re-completing completed fragment should return false") } }) t.Run("unknownToolIdNoCompletion", func(t *testing.T) { _, _, ok, err := tail.CompleteFragment("ch", "nonexistent") if err != nil { t.Fatalf("complete unknown: %v", err) } if ok { t.Error("unknown tool id must not complete") } }) } // --------------------------------------------------------------------------- // API-2: Prepared release, partial confirm, overflow, recovery transitions // --------------------------------------------------------------------------- // TestEvidenceTailConfirmsOnlyReleasedPrefix verifies that PrepareRelease does // not mutate pending/cursor state, and ConfirmRelease only moves the number // of events matching ReleasedEvents into look-behind. func TestEvidenceTailConfirmsOnlyReleasedPrefix(t *testing.T) { textKinds := []EventKind{EventKindTextDelta} req, err := NewFilterHoldRequirementRolling("ch", textKinds, 2) if err != nil { t.Fatalf("build requirement: %v", err) } bind, err := NewFilterHoldBinding("f", req, true) if err != nil { t.Fatalf("build binding: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{bind}) if err != nil { t.Fatalf("build plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("build tail: %v", err) } // Fill to threshold. var lastEpochID uint64 for i := 0; i < 3; i++ { ev, err := NewTextDeltaEvent("ch", "x", now) if err != nil { t.Fatalf("build event: %v", err) } epoch, _, err := tail.Append(ev) if err != nil { t.Fatalf("append: %v", err) } if epoch.Triggered() { lastEpochID = epoch.ID() } } cs := tail.channelState["ch"] beforePending := len(cs.pendingEntries) if lastEpochID == 0 { t.Fatal("expected at least one threshold-triggering epoch") } // PrepareRelease must not mutate pending. pr, err := tail.PrepareRelease(lastEpochID) if err != nil { t.Fatalf("prepare release: %v", err) } if len(cs.pendingEntries) != beforePending { t.Error("PrepareRelease must not change pending count") } if cs.pendingRunes != beforePending { t.Error("PrepareRelease must not change pendingRunes") } releaseEvents := pr.ReleaseEvents() if len(releaseEvents) != 3 { t.Errorf("releaseEvents want 3, got %d", len(releaseEvents)) } t.Run("partialConfirm", func(t *testing.T) { conf := ReleaseConfirmation{ReleasedEvents: 2} if err := tail.ConfirmRelease(pr.Token(), conf); err != nil { t.Fatalf("partial confirm: %v", err) } cs = tail.channelState["ch"] if len(cs.pendingEntries) != 1 { t.Errorf("pending after partial confirm want 1, got %d", len(cs.pendingEntries)) } if cs.pendingRunes != 1 { t.Errorf("pendingRunes after partial confirm want 1, got %d", cs.pendingRunes) } if len(cs.committedLookBehind) != 2 { t.Errorf("lookBehind want 2, got %d", len(cs.committedLookBehind)) } }) // Prepare another release. var lastEpochID2 uint64 for i := 0; i < 1; i++ { ev, err := NewTextDeltaEvent("ch", "y", now) if err != nil { t.Fatalf("build event: %v", err) } epoch, _, err := tail.Append(ev) if err != nil { t.Fatalf("append: %v", err) } if epoch.Triggered() { lastEpochID2 = epoch.ID() } } if lastEpochID2 == 0 { t.Fatal("expected threshold-triggering epoch for second prepare") } prFull, err := tail.PrepareRelease(lastEpochID2) if err != nil { t.Fatalf("prepare: %v", err) } fullSnapshotSize := len(prFull.ReleaseEvents()) t.Run("fullConfirm", func(t *testing.T) { conf := ReleaseConfirmation{ReleasedEvents: fullSnapshotSize} if err := tail.ConfirmRelease(prFull.Token(), conf); err != nil { t.Fatalf("full confirm: %v", err) } cs = tail.channelState["ch"] if len(cs.pendingEntries) != 0 { t.Errorf("pending after full confirm want 0, got %d", len(cs.pendingEntries)) } }) t.Run("zeroConfirmNoMutation", func(t *testing.T) { // Re-add events for zero-confirm (all were released by full confirm). for i := 0; i < 3; i++ { ev, err := NewTextDeltaEvent("ch", "z", now) if err != nil { t.Fatalf("build event: %v", err) } _, _, err = tail.Append(ev) if err != nil { t.Fatalf("append: %v", err) } } // Prepare a fresh release for zero-confirm test. cs = tail.channelState["ch"] var prZeroEpoch uint64 for i := 0; i < 3; i++ { ev, err := NewTextDeltaEvent("ch", "z", now) if err != nil { t.Fatalf("build event: %v", err) } epoch, _, err := tail.Append(ev) if err != nil { t.Fatalf("append: %v", err) } if epoch.Triggered() { prZeroEpoch = epoch.ID() } } if prZeroEpoch == 0 { t.Fatal("expected threshold-triggering epoch for zero-confirm") } prZero, err := tail.PrepareRelease(prZeroEpoch) if err != nil { t.Fatalf("prepare for zero-confirm: %v", err) } // Zero confirm should not change state. prevLookBehind := len(cs.committedLookBehind) err = tail.ConfirmRelease(prZero.Token(), ReleaseConfirmation{ReleasedEvents: 0}) if err != nil { t.Fatalf("zero confirm: %v", err) } if len(cs.committedLookBehind) != prevLookBehind { t.Error("zero confirm must not change look-behind") } }) } // TestEvidenceTailOverflowSignalsNoRelease verifies that exceeding the hard // buffer limit produces an overflow signal without mutating pending state or // producing a release. func TestEvidenceTailOverflowSignalsNoRelease(t *testing.T) { textKinds := []EventKind{EventKindTextDelta} // Use a small maxBuffer to make the test deterministic. req, err := NewFilterHoldRequirementRolling("ch", textKinds, 2) if err != nil { t.Fatalf("build requirement: %v", err) } // Override maxBufferRunes via a custom requirement to keep the test tight. req = FilterHoldRequirement{ channel: "ch", mode: FilterHoldModeRolling, subscribedKinds: textKinds, evidenceRunes: 2, maxBufferRunes: 10, // Minimum valid hard limit. } if err := req.Validate(); err != nil { t.Fatalf("validate custom req: %v", err) } bind, err := NewFilterHoldBinding("f", req, true) if err != nil { t.Fatalf("build binding: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{bind}) if err != nil { t.Fatalf("build plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("build tail: %v", err) } // Fill up to 10 runes (maxBufferRunes limit). for i := 0; i < 10; i++ { ev, err := NewTextDeltaEvent("ch", "x", now) if err != nil { t.Fatalf("build event: %v", err) } _, _, err = tail.Append(ev) if err != nil { t.Fatalf("append: %v", err) } } cs := tail.channelState["ch"] if cs.pendingRunes != 10 { t.Errorf("pendingRunes want 10, got %d", cs.pendingRunes) } // 11th rune → overflow beyond maxBufferRunes=10. ev11, err := NewTextDeltaEvent("ch", "x", now) if err != nil { t.Fatalf("build event: %v", err) } _, signal, err := tail.Append(ev11) if err != nil { t.Fatalf("append overflow: %v", err) } if signal != EvidenceTailSignalBufferOverflow { t.Errorf("signal want bufferOverflow, got %s", signal) } // State must not have changed. cs = tail.channelState["ch"] if cs.pendingRunes != 10 { t.Errorf("overflow must not change pendingRunes, want 10 got %d", cs.pendingRunes) } if len(cs.pendingEntries) != 10 { t.Errorf("overflow must not change pending count, want 10 got %d", len(cs.pendingEntries)) } } // TestEvidenceTailRecoveryTransitionsInvalidatePreparedRelease verifies that // ResetForReplace, PrepareContinuation, and DiscardPendingForTerminal all // invalidate prepared tokens and that their preserve/discard scopes differ. func TestEvidenceTailRecoveryTransitionsInvalidatePreparedRelease(t *testing.T) { textKinds := []EventKind{EventKindTextDelta} req, err := NewFilterHoldRequirementRolling("ch", textKinds, 2) if err != nil { t.Fatalf("build requirement: %v", err) } bind, err := NewFilterHoldBinding("f", req, true) if err != nil { t.Fatalf("build binding: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{bind}) if err != nil { t.Fatalf("build plan: %v", err) } // --- ResetForReplace ------------------------------------------------------ t.Run("ResetForReplace", func(t *testing.T) { tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("build tail: %v", err) } for i := 0; i < 3; i++ { ev, err := NewTextDeltaEvent("ch", "a", now) if err != nil { t.Fatalf("build: %v", err) } _, _, err = tail.Append(ev) if err != nil { t.Fatalf("append: %v", err) } } // Find the latest epoch ID for prepare release. var resetEpoch uint64 for _, rec := range tail.epochs { if rec.epochID > resetEpoch { resetEpoch = rec.epochID } } if resetEpoch == 0 { t.Fatal("expected at least one epoch for ResetForReplace") } pr, err := tail.PrepareRelease(resetEpoch) if err != nil { t.Fatalf("prepare: %v", err) } token := pr.Token() tail.ResetForReplace() // Pending must be cleared. if tail.channelState["ch"] != nil { t.Error("ResetForReplace must clear channel state") } // Look-behind must be cleared too. // Confirm with invalidated token must fail. err = tail.ConfirmRelease(token, ReleaseConfirmation{ReleasedEvents: 1}) if err == nil { t.Error("confirm with invalidated token must fail") } }) // --- PrepareContinuation -------------------------------------------------- t.Run("PrepareContinuationPreservesLookBehind", func(t *testing.T) { tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("build tail: %v", err) } // Add and confirm 2 events. for i := 0; i < 2; i++ { ev, err := NewTextDeltaEvent("ch", "a", now) if err != nil { t.Fatalf("build: %v", err) } _, _, err = tail.Append(ev) if err != nil { t.Fatalf("append: %v", err) } } // Find the latest epoch ID for prepare release. var contEpoch uint64 for _, rec := range tail.epochs { if rec.epochID > contEpoch { contEpoch = rec.epochID } } if contEpoch == 0 { t.Fatal("expected at least one epoch for PrepareContinuation") } pr, err := tail.PrepareRelease(contEpoch) if err != nil { t.Fatalf("prepare: %v", err) } token := pr.Token() // Confirm 1 event to create look-behind. if err := tail.ConfirmRelease(token, ReleaseConfirmation{ReleasedEvents: 1}); err != nil { t.Fatalf("confirm: %v", err) } // Add another event to have pending. ev, err := NewTextDeltaEvent("ch", "b", now) if err != nil { t.Fatalf("build: %v", err) } _, _, err = tail.Append(ev) if err != nil { t.Fatalf("append: %v", err) } tail.PrepareContinuation() cs := tail.channelState["ch"] // Pending must be cleared. if len(cs.pendingEntries) != 0 { t.Errorf("pending must be cleared, got %d", len(cs.pendingEntries)) } if cs.pendingRunes != 0 { t.Errorf("pendingRunes must be 0, got %d", cs.pendingRunes) } // Look-behind must be preserved. if len(cs.committedLookBehind) != 1 { t.Errorf("lookBehind want 1 preserved, got %d", len(cs.committedLookBehind)) } }) // --- DiscardPendingForTerminal -------------------------------------------- t.Run("DiscardPendingForTerminalPreservesLookBehind", func(t *testing.T) { tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("build tail: %v", err) } for i := 0; i < 3; i++ { ev, err := NewTextDeltaEvent("ch", "a", now) if err != nil { t.Fatalf("build: %v", err) } _, _, err = tail.Append(ev) if err != nil { t.Fatalf("append: %v", err) } } cs := tail.channelState["ch"] // Find the latest epoch ID for prepare release. var discEpoch uint64 for _, rec := range tail.epochs { if rec.epochID > discEpoch { discEpoch = rec.epochID } } if discEpoch == 0 { t.Fatal("expected at least one epoch for DiscardPendingForTerminal") } pr, err := tail.PrepareRelease(discEpoch) if err != nil { t.Fatalf("prepare: %v", err) } token := pr.Token() // Confirm 2. if err := tail.ConfirmRelease(token, ReleaseConfirmation{ReleasedEvents: 2}); err != nil { t.Fatalf("confirm: %v", err) } // Add pending event. ev, err := NewTextDeltaEvent("ch", "c", now) if err != nil { t.Fatalf("build: %v", err) } _, _, err = tail.Append(ev) if err != nil { t.Fatalf("append: %v", err) } tail.DiscardPendingForTerminal() cs = tail.channelState["ch"] // Pending cleared. if len(cs.pendingEntries) != 0 { t.Errorf("pending must be cleared, got %d", len(cs.pendingEntries)) } // Look-behind preserved. if len(cs.committedLookBehind) != 2 { t.Errorf("lookBehind want 2, got %d", len(cs.committedLookBehind)) } // Prepared token invalidated. err = tail.ConfirmRelease(token, ReleaseConfirmation{ReleasedEvents: 1}) if err == nil { t.Error("confirm with invalidated token must fail") } }) t.Run("staleTokenRejected", func(t *testing.T) { tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("build tail: %v", err) } for i := 0; i < 3; i++ { ev, err := NewTextDeltaEvent("ch", "a", now) if err != nil { t.Fatalf("build: %v", err) } _, _, err = tail.Append(ev) if err != nil { t.Fatalf("append: %v", err) } } // Find the latest epoch ID for prepare release. var staleEpoch uint64 for _, rec := range tail.epochs { if rec.epochID > staleEpoch { staleEpoch = rec.epochID } } if staleEpoch == 0 { t.Fatal("expected at least one epoch for staleTokenRejected") } pr, err := tail.PrepareRelease(staleEpoch) if err != nil { t.Fatalf("prepare: %v", err) } // Reset invalidates the token. tail.ResetForReplace() // Confirm with stale token must fail. err = tail.ConfirmRelease(pr.Token(), ReleaseConfirmation{ReleasedEvents: 1}) if err == nil { t.Error("stale token confirm must fail") } }) t.Run("duplicateConfirmRejected", func(t *testing.T) { tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("build tail: %v", err) } for i := 0; i < 3; i++ { ev, err := NewTextDeltaEvent("ch", "a", now) if err != nil { t.Fatalf("build: %v", err) } _, _, err = tail.Append(ev) if err != nil { t.Fatalf("append: %v", err) } } // Find the latest epoch ID for prepare release. var dupEpoch uint64 for _, rec := range tail.epochs { if rec.epochID > dupEpoch { dupEpoch = rec.epochID } } if dupEpoch == 0 { t.Fatal("expected at least one epoch for duplicateConfirmRejected") } pr, err := tail.PrepareRelease(dupEpoch) if err != nil { t.Fatalf("prepare: %v", err) } // First confirm succeeds. if err := tail.ConfirmRelease(pr.Token(), ReleaseConfirmation{ReleasedEvents: 3}); err != nil { t.Fatalf("first confirm: %v", err) } // Duplicate confirm must fail. err = tail.ConfirmRelease(pr.Token(), ReleaseConfirmation{ReleasedEvents: 0}) if err == nil { t.Error("duplicate confirm must fail") } }) } // --------------------------------------------------------------------------- // REVIEW_API-1: Deterministic composition, defensive copy, fail-closed validation // --------------------------------------------------------------------------- // TestFilterHoldRequirementValidatesPublicBoundMatrix verifies that the // constructor with explicit max_buffer_runes override validates the public // bound matrix: default, override, min, max, and reachable threshold. func TestFilterHoldRequirementValidatesPublicBoundMatrix(t *testing.T) { textKinds := []EventKind{EventKindTextDelta, EventKindReasoningDelta} t.Run("defaultRolling", func(t *testing.T) { _, err := NewFilterHoldRequirementRolling("ch", textKinds, 500) if err != nil { t.Fatalf("default rolling: %v", err) } }) t.Run("overrideWithMaxBuffer", func(t *testing.T) { r, err := NewFilterHoldRequirementRollingWithMaxBuffer("ch", textKinds, 200, 2048) if err != nil { t.Fatalf("override rolling: %v", err) } if r.MaxBufferRunes() != 2048 { t.Errorf("buffer want 2048, got %d", r.MaxBufferRunes()) } if r.EvidenceRunes() != 200 { t.Errorf("evidence want 200, got %d", r.EvidenceRunes()) } }) t.Run("minBoundary", func(t *testing.T) { _, err := NewFilterHoldRequirementRollingWithMaxBuffer("ch", textKinds, minEvidenceRunes, minMaxBufferRunes) if err != nil { t.Fatalf("min boundary: %v", err) } }) t.Run("maxBoundary", func(t *testing.T) { _, err := NewFilterHoldRequirementRollingWithMaxBuffer("ch", textKinds, maxEvidenceRunes, maxMaxBufferRunes) if err != nil { t.Fatalf("max boundary: %v", err) } }) t.Run("thresholdEqualToBuffer", func(t *testing.T) { // evidence_runes == max_buffer_runes is allowed (reachable threshold). _, err := NewFilterHoldRequirementRollingWithMaxBuffer("ch", textKinds, 100, 100) if err != nil { t.Fatalf("threshold==buffer: %v", err) } }) t.Run("bufferLessThanThresholdRejected", func(t *testing.T) { _, err := NewFilterHoldRequirementRollingWithMaxBuffer("ch", textKinds, 200, 100) if err == nil { t.Error("buffer < threshold should be rejected") } }) t.Run("terminalGateOverride", func(t *testing.T) { _, err := NewFilterHoldRequirementTerminalGateWithMaxBuffer("ch", textKinds, EventKindTerminal, 8192) if err != nil { t.Fatalf("terminal gate override: %v", err) } }) t.Run("fragmentGateOverride", func(t *testing.T) { fragKinds := []EventKind{EventKindToolCallFragment} _, err := NewFilterHoldRequirementFragmentGateWithMaxBuffer("ch", fragKinds, EventKindToolCallFragment, 4096) if err != nil { t.Fatalf("fragment gate override: %v", err) } }) } // TestEvidencePlanComposesDeterministicallyAndDefensively verifies that // binding order reversal produces identical results and that returned slices // are defensive copies that cannot mutate the plan. func TestEvidencePlanComposesDeterministicallyAndDefensively(t *testing.T) { textKinds := []EventKind{EventKindTextDelta, EventKindReasoningDelta} t.Run("bindingOrderReversal", func(t *testing.T) { rollA, err := NewFilterHoldRequirementRollingWithMaxBuffer("ch", textKinds, 300, 5000) if err != nil { t.Fatalf("build rollA: %v", err) } rollB, err := NewFilterHoldRequirementRollingWithMaxBuffer("ch", textKinds, 400, 3000) if err != nil { t.Fatalf("build rollB: %v", err) } b1, err := NewFilterHoldBinding("f1", rollA, true) if err != nil { t.Fatalf("build binding 1: %v", err) } b2, err := NewFilterHoldBinding("f2", rollB, true) if err != nil { t.Fatalf("build binding 2: %v", err) } planForward, err := NewEvidencePlan([]FilterHoldBinding{b1, b2}) if err != nil { t.Fatalf("forward plan: %v", err) } planReverse, err := NewEvidencePlan([]FilterHoldBinding{b2, b1}) if err != nil { t.Fatalf("reverse plan: %v", err) } reqF, _ := planForward.BlockingRequirement("ch") reqR, _ := planReverse.BlockingRequirement("ch") if reqF.EvidenceRunes() != reqR.EvidenceRunes() { t.Errorf("evidenceRunes mismatch: forward=%d reverse=%d", reqF.EvidenceRunes(), reqR.EvidenceRunes()) } if reqF.MaxBufferRunes() != reqR.MaxBufferRunes() { t.Errorf("maxBufferRunes mismatch: forward=%d reverse=%d", reqF.MaxBufferRunes(), reqR.MaxBufferRunes()) } }) t.Run("returnedSliceMutationIsolation", func(t *testing.T) { roll, err := NewFilterHoldRequirementRolling("ch", textKinds, 500) if err != nil { t.Fatalf("build: %v", err) } bind, err := NewFilterHoldBinding("f", roll, true) if err != nil { t.Fatalf("build binding: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{bind}) if err != nil { t.Fatalf("plan: %v", err) } kinds := plan.SubscribeKinds("ch") originalLen := len(kinds) originalFirst := kinds[0] // Mutate the returned slice. kinds[0] = "" // Second call should return a fresh copy. kinds2 := plan.SubscribeKinds("ch") if len(kinds2) != originalLen { t.Error("returned slice length changed") } if kinds2[0] != originalFirst { t.Errorf("returned slice was mutated, got %q want %q", kinds2[0], originalFirst) } }) t.Run("incompatibleBoundsRejected", func(t *testing.T) { // Two rolling requirements on same channel where merged buffer < merged threshold. r1, err := NewFilterHoldRequirementRollingWithMaxBuffer("ch", textKinds, 500, 600) if err != nil { t.Fatalf("build r1: %v", err) } r2, err := NewFilterHoldRequirementRollingWithMaxBuffer("ch", textKinds, 700, 800) if err != nil { t.Fatalf("build r2: %v", err) } b1, err := NewFilterHoldBinding("f1", r1, true) if err != nil { t.Fatalf("build b1: %v", err) } b2, err := NewFilterHoldBinding("f2", r2, true) if err != nil { t.Fatalf("build b2: %v", err) } _, err = NewEvidencePlan([]FilterHoldBinding{b1, b2}) if err == nil { t.Error("merged buffer (600) < merged threshold (700) should be rejected") } }) } // TestEvidenceTailAppliesSubscriptionsAfterFailClosedValidation verifies that // unsubscribed events do not mutate hold state and that invalid UTF-8 is // rejected before any subscription check (fail-closed). func TestEvidenceTailAppliesSubscriptionsAfterFailClosedValidation(t *testing.T) { textKinds := []EventKind{EventKindTextDelta} req, err := NewFilterHoldRequirementRolling("ch", textKinds, 10) if err != nil { t.Fatalf("build requirement: %v", err) } bind, err := NewFilterHoldBinding("f", req, true) if err != nil { t.Fatalf("build binding: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{bind}) if err != nil { t.Fatalf("build plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("build tail: %v", err) } t.Run("unsubscribedEventNoStateMutation", func(t *testing.T) { // reasoning_delta is not subscribed. ev, err := NewReasoningDeltaEvent("ch", "hello world!!!", now) if err != nil { t.Fatalf("build event: %v", err) } _, signal, err := tail.Append(ev) if err != nil { t.Fatalf("append unsubscribed: %v", err) } if signal != EvidenceTailSignalNone { t.Errorf("signal want none, got %s", signal) } // No channel state should be created. if tail.channelState["ch"] != nil { t.Error("unsubscribed event must not create channel state") } }) t.Run("observeOnlyInvalidUTF8Rejected", func(t *testing.T) { // Build a plan with both text_delta (subscribed) and reasoning_delta (observe-only). allKinds := []EventKind{EventKindTextDelta, EventKindReasoningDelta} req2, err := NewFilterHoldRequirementRolling("ch2", allKinds, 10) if err != nil { t.Fatalf("build req2: %v", err) } bind2, err := NewFilterHoldBinding("f2", req2, true) if err != nil { t.Fatalf("build bind2: %v", err) } plan2, err := NewEvidencePlan([]FilterHoldBinding{bind2}) if err != nil { t.Fatalf("build plan2: %v", err) } tail2, err := NewEvidenceTail(plan2) if err != nil { t.Fatalf("build tail2: %v", err) } // Invalid UTF-8 for a subscribed kind must be rejected (fail-closed). invalidUTF8 := string([]byte{0xFF, 0xFE}) ev, err := NewTextDeltaEvent("ch2", invalidUTF8, now) if err != nil { t.Fatalf("build invalid event: %v", err) } _, _, err = tail2.Append(ev) if err == nil { t.Error("invalid UTF-8 must be rejected") } // State must not have been mutated. cs := tail2.channelState["ch2"] if cs != nil && cs.pendingRunes != 0 { t.Errorf("state must not be mutated, pendingRunes=%d", cs.pendingRunes) } }) t.Run("noHoldInvalidUTF8Rejected", func(t *testing.T) { // None mode with no blocking requirement. noneKinds := []EventKind{EventKindTextDelta} noneReq, err := NewFilterHoldRequirementNone("ch3", noneKinds) if err != nil { t.Fatalf("build none: %v", err) } noneBind, err := NewFilterHoldBinding("f3", noneReq, true) if err != nil { t.Fatalf("build none bind: %v", err) } nonePlan, err := NewEvidencePlan([]FilterHoldBinding{noneBind}) if err != nil { t.Fatalf("build none plan: %v", err) } noneTail, err := NewEvidenceTail(nonePlan) if err != nil { t.Fatalf("build none tail: %v", err) } // Invalid UTF-8 in no-blocking path must also be rejected (fail-closed). invalidUTF8 := string([]byte{0xFF, 0xFE}) ev, err := NewTextDeltaEvent("ch3", invalidUTF8, now) if err != nil { t.Fatalf("build invalid event: %v", err) } _, _, err = noneTail.Append(ev) if err == nil { t.Error("invalid UTF-8 must be rejected even in no-blocking path") } }) } // --------------------------------------------------------------------------- // REVIEW_API-2: Epoch-bound snapshot, committed cursor, exact look-behind // --------------------------------------------------------------------------- // TestEvidenceTailBindsPreparedReleaseToEpochSnapshot verifies that the prepared // release is bound to the epoch and that post-prepare appends are excluded // from the snapshot regardless of map iteration order. The evidence_runes // window is large enough to retain all released events in look-behind so the // snapshot-size invariant—not the trim bound—is what is under test here. func TestEvidenceTailBindsPreparedReleaseToEpochSnapshot(t *testing.T) { textKinds := []EventKind{EventKindTextDelta} // evidence_runes=30 matches the total rune count of three 10-rune events // so the threshold triggers on the third append and the look-behind trim // bound retains all three confirmed events. req, err := NewFilterHoldRequirementRollingWithMaxBuffer("ch", textKinds, 30, 4096) if err != nil { t.Fatalf("build requirement: %v", err) } bind, err := NewFilterHoldBinding("f", req, true) if err != nil { t.Fatalf("build binding: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{bind}) if err != nil { t.Fatalf("build plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("build tail: %v", err) } // Fill to threshold. for i := 0; i < 3; i++ { ev, err := NewTextDeltaEvent("ch", repeatRune('a', 10), now) if err != nil { t.Fatalf("build event %d: %v", i, err) } _, _, _ = tail.Append(ev) if err != nil { t.Fatalf("append %d: %v", i, err) } } cs := tail.channelState["ch"] if cs.pendingRunes != 30 { t.Fatalf("pendingRunes want 30, got %d", cs.pendingRunes) } // Prepare release. var snapshotEpoch uint64 for _, rec := range tail.epochs { if rec.epochID > snapshotEpoch { snapshotEpoch = rec.epochID } } pr, err := tail.PrepareRelease(snapshotEpoch) if err != nil { t.Fatalf("prepare: %v", err) } releaseEvents := pr.ReleaseEvents() if len(releaseEvents) != 3 { t.Errorf("releaseEvents want 3, got %d", len(releaseEvents)) } // Append another event after prepare. evExtra, err := NewTextDeltaEvent("ch", repeatRune('z', 10), now) if err != nil { t.Fatalf("build extra: %v", err) } tail.Append(evExtra) cs = tail.channelState["ch"] if len(cs.pendingEntries) != 4 { t.Fatalf("pendingEntries want 4 after extra append, got %d", len(cs.pendingEntries)) } // Confirm only the 3 events from the snapshot, not the extra. err = tail.ConfirmRelease(pr.Token(), ReleaseConfirmation{ReleasedEvents: 3}) if err != nil { t.Fatalf("confirm snapshot: %v", err) } cs = tail.channelState["ch"] // The extra event should remain in pending. if len(cs.pendingEntries) != 1 { t.Errorf("pending want 1 (extra only), got %d", len(cs.pendingEntries)) } // Look-behind should have exactly 3 events. if len(cs.committedLookBehind) != 3 { t.Errorf("lookBehind want 3, got %d", len(cs.committedLookBehind)) } } // TestEvidenceTailRejectsOverlappingAndOversizedConfirmation verifies that // concurrent prepared tokens are rejected and that oversized confirmation is // refused. func TestEvidenceTailRejectsOverlappingAndOversizedConfirmation(t *testing.T) { textKinds := []EventKind{EventKindTextDelta} req, err := NewFilterHoldRequirementRolling("ch", textKinds, 2) if err != nil { t.Fatalf("build requirement: %v", err) } bind, err := NewFilterHoldBinding("f", req, true) if err != nil { t.Fatalf("build binding: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{bind}) if err != nil { t.Fatalf("build plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("build tail: %v", err) } // Fill to threshold. for i := 0; i < 3; i++ { ev, err := NewTextDeltaEvent("ch", "x", now) if err != nil { t.Fatalf("build: %v", err) } _, _, _ = tail.Append(ev) if err != nil { t.Fatalf("append: %v", err) } } // Prepare release. var prepareEpoch uint64 for _, rec := range tail.epochs { if rec.epochID > prepareEpoch { prepareEpoch = rec.epochID } } pr, err := tail.PrepareRelease(prepareEpoch) if err != nil { t.Fatalf("prepare: %v", err) } t.Run("overConfirmationRejected", func(t *testing.T) { // Try to confirm more than the snapshot size (3 events). err := tail.ConfirmRelease(pr.Token(), ReleaseConfirmation{ReleasedEvents: 5}) if err == nil { t.Error("over-confirmation must be rejected") } }) t.Run("duplicatePrepareRejected", func(t *testing.T) { // Confirm the outer prepare so preparedByChannel is cleared. err = tail.ConfirmRelease(pr.Token(), ReleaseConfirmation{ReleasedEvents: 3}) if err != nil { t.Fatalf("confirm outer prepare: %v", err) } // Re-fill and prepare again. for i := 0; i < 3; i++ { ev, err := NewTextDeltaEvent("ch", "y", now) if err != nil { t.Fatalf("build: %v", err) } _, _, _ = tail.Append(ev) if err != nil { t.Fatalf("append: %v", err) } } var epoch2 uint64 for _, rec := range tail.epochs { if rec.epochID > epoch2 { epoch2 = rec.epochID } } _, err = tail.PrepareRelease(epoch2) if err != nil { t.Fatalf("prepare 2: %v", err) } // Try to prepare again with the same epoch while it's already prepared. _, err = tail.PrepareRelease(epoch2) if err == nil { t.Error("duplicate prepare with same epoch must be rejected") } }) } // TestEvidenceTailPreservesCursorAndExactRuneLookBehind verifies that partial // and full confirm updates the committed cursor correctly, that oversized // events are sliced at rune boundaries, and that replace/continuation behave // differently with respect to cursor preservation. func TestEvidenceTailPreservesCursorAndExactRuneLookBehind(t *testing.T) { textKinds := []EventKind{EventKindTextDelta} req, err := NewFilterHoldRequirementRolling("ch", textKinds, 5) if err != nil { t.Fatalf("build requirement: %v", err) } bind, err := NewFilterHoldBinding("f", req, true) if err != nil { t.Fatalf("build binding: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{bind}) if err != nil { t.Fatalf("build plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("build tail: %v", err) } t.Run("partialConfirmIncrementsCursor", func(t *testing.T) { for i := 0; i < 4; i++ { ev, err := NewTextDeltaEvent("ch", repeatRune('a', 2), now) if err != nil { t.Fatalf("build: %v", err) } _, _, err = tail.Append(ev) if err != nil { t.Fatalf("append: %v", err) } } var epochID uint64 for _, rec := range tail.epochs { if rec.epochID > epochID { epochID = rec.epochID } } pr, err := tail.PrepareRelease(epochID) if err != nil { t.Fatalf("prepare: %v", err) } if tail.CommittedCursor("ch") != 0 { t.Errorf("initial cursor want 0, got %d", tail.CommittedCursor("ch")) } err = tail.ConfirmRelease(pr.Token(), ReleaseConfirmation{ReleasedEvents: 2}) if err != nil { t.Fatalf("partial confirm: %v", err) } if tail.CommittedCursor("ch") != 2 { t.Errorf("cursor after partial want 2, got %d", tail.CommittedCursor("ch")) } }) t.Run("fullConfirmConsumesEpoch", func(t *testing.T) { for i := 0; i < 3; i++ { ev, err := NewTextDeltaEvent("ch", repeatRune('b', 2), now) if err != nil { t.Fatalf("build: %v", err) } _, _, err = tail.Append(ev) if err != nil { t.Fatalf("append: %v", err) } } var epochID uint64 for _, rec := range tail.epochs { if rec.epochID > epochID { epochID = rec.epochID } } pr, err := tail.PrepareRelease(epochID) if err != nil { t.Fatalf("prepare: %v", err) } err = tail.ConfirmRelease(pr.Token(), ReleaseConfirmation{ReleasedEvents: 3}) if err != nil { t.Fatalf("full confirm: %v", err) } if tail.CommittedCursor("ch") != 5 { t.Errorf("cursor after full want 5, got %d", tail.CommittedCursor("ch")) } }) t.Run("exactRuneSuffixFromOversizedEvent", func(t *testing.T) { req2, err := NewFilterHoldRequirementRollingWithMaxBuffer("ch2", textKinds, 2, 15) if err != nil { t.Fatalf("build req2: %v", err) } bind2, err := NewFilterHoldBinding("f2", req2, true) if err != nil { t.Fatalf("build bind2: %v", err) } plan2, err := NewEvidencePlan([]FilterHoldBinding{bind2}) if err != nil { t.Fatalf("build plan2: %v", err) } tail2, err := NewEvidenceTail(plan2) if err != nil { t.Fatalf("build tail2: %v", err) } for i := 0; i < 5; i++ { ev, err := NewTextDeltaEvent("ch2", repeatRune('c', 2), now) if err != nil { t.Fatalf("build: %v", err) } _, _, err = tail2.Append(ev) if err != nil { t.Fatalf("append: %v", err) } } for i := 0; i < 1; i++ { ev, err := NewTextDeltaEvent("ch2", "d", now) if err != nil { t.Fatalf("build: %v", err) } _, _, err = tail2.Append(ev) if err != nil { t.Fatalf("append threshold: %v", err) } } var epochID uint64 for _, rec := range tail2.epochs { if rec.epochID > epochID { epochID = rec.epochID } } pr, err := tail2.PrepareRelease(epochID) if err != nil { t.Fatalf("prepare: %v", err) } err = tail2.ConfirmRelease(pr.Token(), ReleaseConfirmation{ReleasedEvents: len(pr.ReleaseEvents())}) if err != nil { t.Fatalf("confirm: %v", err) } cs := tail2.channelState["ch2"] if cs == nil { t.Fatal("channel state missing") } totalRunes := 0 for _, ev := range cs.committedLookBehind { totalRunes += runeCountForEvent(ev) } if totalRunes > 15 { t.Errorf("lookBehind runes %d exceeds maxBufferRunes 15", totalRunes) } }) t.Run("replaceClearsCursor", func(t *testing.T) { req2, err := NewFilterHoldRequirementRolling("ch_r", textKinds, 5) if err != nil { t.Fatalf("build req2: %v", err) } bind2, err := NewFilterHoldBinding("f2", req2, true) if err != nil { t.Fatalf("build bind2: %v", err) } plan2, err := NewEvidencePlan([]FilterHoldBinding{bind2}) if err != nil { t.Fatalf("build plan2: %v", err) } tail2, err := NewEvidenceTail(plan2) if err != nil { t.Fatalf("build tail2: %v", err) } for i := 0; i < 3; i++ { ev, err := NewTextDeltaEvent("ch_r", repeatRune('e', 2), now) if err != nil { t.Fatalf("build: %v", err) } _, _, err = tail2.Append(ev) if err != nil { t.Fatalf("append: %v", err) } } var epochID uint64 for _, rec := range tail2.epochs { if rec.epochID > epochID { epochID = rec.epochID } } pr, err := tail2.PrepareRelease(epochID) if err != nil { t.Fatalf("prepare: %v", err) } tail2.ConfirmRelease(pr.Token(), ReleaseConfirmation{ReleasedEvents: 2}) if tail2.CommittedCursor("ch_r") != 2 { t.Fatalf("cursor before replace want 2, got %d", tail2.CommittedCursor("ch_r")) } tail2.ResetForReplace() if tail2.CommittedCursor("ch_r") != 0 { t.Errorf("cursor after replace want 0, got %d", tail2.CommittedCursor("ch_r")) } }) t.Run("continuationPreservesCursor", func(t *testing.T) { req3, err := NewFilterHoldRequirementRolling("ch_c", textKinds, 5) if err != nil { t.Fatalf("build req3: %v", err) } bind3, err := NewFilterHoldBinding("f3", req3, true) if err != nil { t.Fatalf("build bind3: %v", err) } plan3, err := NewEvidencePlan([]FilterHoldBinding{bind3}) if err != nil { t.Fatalf("build plan3: %v", err) } tail3, err := NewEvidenceTail(plan3) if err != nil { t.Fatalf("build tail3: %v", err) } for i := 0; i < 3; i++ { ev, err := NewTextDeltaEvent("ch_c", repeatRune('f', 2), now) if err != nil { t.Fatalf("build: %v", err) } _, _, err = tail3.Append(ev) if err != nil { t.Fatalf("append: %v", err) } } var epochID uint64 for _, rec := range tail3.epochs { if rec.epochID > epochID { epochID = rec.epochID } } pr, err := tail3.PrepareRelease(epochID) if err != nil { t.Fatalf("prepare: %v", err) } tail3.ConfirmRelease(pr.Token(), ReleaseConfirmation{ReleasedEvents: 1}) if tail3.CommittedCursor("ch_c") != 1 { t.Fatalf("cursor before continuation want 1, got %d", tail3.CommittedCursor("ch_c")) } tail3.PrepareContinuation() if tail3.CommittedCursor("ch_c") != 1 { t.Errorf("cursor after continuation want 1, got %d", tail3.CommittedCursor("ch_c")) } cs := tail3.channelState["ch_c"] if len(cs.pendingEntries) != 0 { t.Errorf("pending after continuation want 0, got %d", len(cs.pendingEntries)) } }) } // TestEvidenceTailFragmentsReleaseOnlyCompletedSafePrefix verifies that when // fragments A and B are interleaved, completing only A produces an epoch for // the safe prefix up to A2 (the first incomplete is B1), and completing B // produces an epoch for all four events. func TestEvidenceTailFragmentsReleaseOnlyCompletedSafePrefix(t *testing.T) { toolKinds := []EventKind{EventKindToolCallFragment} req, err := NewFilterHoldRequirementFragmentGate("ch", toolKinds, EventKindToolCallFragment) if err != nil { t.Fatalf("build requirement: %v", err) } bind, err := NewFilterHoldBinding("f", req, true) if err != nil { t.Fatalf("build binding: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{bind}) if err != nil { t.Fatalf("build plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("build tail: %v", err) } // Interleave A and B. evA1, _ := NewToolCallFragmentEvent("ch", "call-A", "funcA", `{"a":1}`, now) tail.Append(evA1) evB1, _ := NewToolCallFragmentEvent("ch", "call-B", "funcB", `{"b":2}`, now) tail.Append(evB1) evA2, _ := NewToolCallFragmentEvent("ch", "call-A", "funcA", `{"a":3}`, now) tail.Append(evA2) evB2, _ := NewToolCallFragmentEvent("ch", "call-B", "funcB", `{"b":4}`, now) tail.Append(evB2) if len(tail.channelState["ch"].pendingEntries) != 4 { t.Fatalf("pending want 4, got %d", len(tail.channelState["ch"].pendingEntries)) } // Complete A only — the safe prefix includes entries 0..2 (A1, B1, A2). epochA, signalA, okA, err := tail.CompleteFragment("ch", "call-A") if err != nil { t.Fatalf("complete A: %v", err) } if !okA { t.Error("complete A should return true") } if signalA != EvidenceTailSignalTrigger { t.Errorf("complete A signal want trigger, got %s", signalA) } if epochA.ID() == 0 { t.Fatal("complete A should produce an epoch") } // Safe prefix stops at B1 (entry index 1, first incomplete fragment). prA, err := tail.PrepareRelease(epochA.ID()) if err != nil { t.Fatalf("prepare after A complete: %v", err) } if len(prA.ReleaseEvents()) != 1 { t.Errorf("safe prefix with only A complete want 1 event (A1), got %d", len(prA.ReleaseEvents())) } // Complete B — all fragments are now complete, so all 4 entries form the // safe prefix for the new epoch. epochAB, signalAB, okAB, err := tail.CompleteFragment("ch", "call-B") if err != nil { t.Fatalf("complete B: %v", err) } if !okAB { t.Error("complete B should return true") } if signalAB != EvidenceTailSignalTrigger { t.Errorf("complete B signal want trigger, got %s", signalAB) } if epochAB.ID() == 0 { t.Fatal("complete B should produce an epoch") } // All fragments complete → all 4 events released. prAB, err := tail.PrepareRelease(epochAB.ID()) if err != nil { t.Fatalf("prepare after both complete: %v", err) } if len(prAB.ReleaseEvents()) != 4 { t.Errorf("all complete want 4 events, got %d", len(prAB.ReleaseEvents())) } // PrepareRelease for the first epoch should now fail (its snapshot range // has been invalidated by the second completion). _, err = tail.PrepareRelease(epochA.ID()) if err == nil { t.Error("prepareRelease for consumed epoch should fail") } } // TestEvidenceTailAccumulatesAndBoundsFragmentByToolCall verifies that multiple // fragments with the same toolCallID accumulate and that exceeding the per-ID // bound prevents state creation. func TestEvidenceTailAccumulatesAndBoundsFragmentByToolCall(t *testing.T) { toolKinds := []EventKind{EventKindToolCallFragment} req, err := NewFilterHoldRequirementFragmentGateWithMaxBuffer("ch", toolKinds, EventKindToolCallFragment, 50) if err != nil { t.Fatalf("build requirement: %v", err) } bind, err := NewFilterHoldBinding("f", req, true) if err != nil { t.Fatalf("build binding: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{bind}) if err != nil { t.Fatalf("build plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("build tail: %v", err) } t.Run("sameIdMultipleFragmentsAccumulate", func(t *testing.T) { for i := 0; i < 3; i++ { ev, err := NewToolCallFragmentEvent("ch", "call-same", "func", fmt.Sprintf(`{"n":%d}`, i), now) if err != nil { t.Fatalf("build fragment %d: %v", i, err) } _, _, err = tail.Append(ev) if err != nil { t.Fatalf("append fragment %d: %v", i, err) } } // Complete the fragment — since all entries are for call-same and // there are no incomplete fragments, the safe prefix includes all 3. epoch, _, ok, err := tail.CompleteFragment("ch", "call-same") if err != nil { t.Fatalf("complete: %v", err) } if !ok { t.Error("complete should return true") } if epoch.ID() == 0 { t.Fatal("complete should produce an epoch") } pr, err := tail.PrepareRelease(epoch.ID()) if err != nil { t.Fatalf("prepare: %v", err) } if len(pr.ReleaseEvents()) != 3 { t.Errorf("releaseEvents want 3, got %d", len(pr.ReleaseEvents())) } }) t.Run("perIdOverflowNoRelease", func(t *testing.T) { req2, err := NewFilterHoldRequirementFragmentGateWithMaxBuffer("ch2", toolKinds, EventKindToolCallFragment, 10) if err != nil { t.Fatalf("build req2: %v", err) } bind2, err := NewFilterHoldBinding("f2", req2, true) if err != nil { t.Fatalf("build bind2: %v", err) } plan2, err := NewEvidencePlan([]FilterHoldBinding{bind2}) if err != nil { t.Fatalf("build plan2: %v", err) } tail2, err := NewEvidenceTail(plan2) if err != nil { t.Fatalf("build tail2: %v", err) } for i := 0; i < 6; i++ { ev, err := NewToolCallFragmentEvent("ch2", "call-big", "func", "abcd", now) if err != nil { t.Fatalf("build fragment: %v", err) } _, signal, err := tail2.Append(ev) if err != nil { t.Fatalf("append fragment: %v", err) } if i == 5 && signal != EvidenceTailSignalBufferOverflow { t.Errorf("sixth fragment signal want overflow, got %s", signal) } } cs := tail2.channelState["ch2"] if cs != nil { fragment := cs.fragmentState["call-big"] if fragment != nil && fragment.runes > 10 { t.Errorf("fragment runes %d exceeds per-ID bound 10", fragment.runes) } } }) } // TestEvidencePlanComposesMixedRequirementsDeterministically verifies that // mixing rolling, terminal_gate, and fragment_gate bindings on the same // channel produces a deterministic merged requirement regardless of input // order. Mode priority, subscription-kind union, min positive hard bound, // and same-mode trigger compatibility are all checked. Regressions for // reverse ordering and trigger conflicts are included. func TestEvidencePlanComposesMixedRequirementsDeterministically(t *testing.T) { textKinds := []EventKind{EventKindTextDelta, EventKindReasoningDelta} toolKinds := []EventKind{EventKindToolCallFragment} t.Run("rollingThenFragment", func(t *testing.T) { r, err := NewFilterHoldRequirementRollingWithMaxBuffer("ch", textKinds, 300, 5000) if err != nil { t.Fatalf("build rolling: %v", err) } f, err := NewFilterHoldRequirementFragmentGate("ch", toolKinds, EventKindToolCallFragment) if err != nil { t.Fatalf("build fragment: %v", err) } b1, err := NewFilterHoldBinding("f1", r, true) if err != nil { t.Fatalf("build binding 1: %v", err) } b2, err := NewFilterHoldBinding("f2", f, true) if err != nil { t.Fatalf("build binding 2: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{b1, b2}) if err != nil { t.Fatalf("NewEvidencePlan: %v", err) } req, _ := plan.BlockingRequirement("ch") if req.Mode() != FilterHoldModeFragmentGate { t.Errorf("strongest mode want fragment_gate, got %s", req.Mode()) } if req.MaxBufferRunes() != 4096 { // min(5000, default 4096) t.Errorf("min hard bound want 4096, got %d", req.MaxBufferRunes()) } kinds := plan.BlockingKinds("ch") if len(kinds) != 3 { t.Errorf("blocking kinds want 3, got %d", len(kinds)) } }) t.Run("fragmentThenRolling", func(t *testing.T) { f, err := NewFilterHoldRequirementFragmentGate("ch", toolKinds, EventKindToolCallFragment) if err != nil { t.Fatalf("build fragment: %v", err) } r, err := NewFilterHoldRequirementRollingWithMaxBuffer("ch", textKinds, 300, 5000) if err != nil { t.Fatalf("build rolling: %v", err) } b1, err := NewFilterHoldBinding("f1", f, true) if err != nil { t.Fatalf("build binding 1: %v", err) } b2, err := NewFilterHoldBinding("f2", r, true) if err != nil { t.Fatalf("build binding 2: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{b1, b2}) if err != nil { t.Fatalf("NewEvidencePlan: %v", err) } req, _ := plan.BlockingRequirement("ch") if req.Mode() != FilterHoldModeFragmentGate { t.Errorf("strongest mode want fragment_gate, got %s", req.Mode()) } if req.MaxBufferRunes() != 4096 { t.Errorf("min hard bound want 4096, got %d", req.MaxBufferRunes()) } }) t.Run("terminalWithFragmentTriggerConflict", func(t *testing.T) { f, err := NewFilterHoldRequirementFragmentGate("ch", toolKinds, EventKindToolCallFragment) if err != nil { t.Fatalf("build fragment: %v", err) } te, err := NewFilterHoldRequirementTerminalGate("ch", textKinds, EventKindTerminal) if err != nil { t.Fatalf("build terminal: %v", err) } b1, err := NewFilterHoldBinding("f1", f, true) if err != nil { t.Fatalf("build binding 1: %v", err) } b2, err := NewFilterHoldBinding("f2", te, true) if err != nil { t.Fatalf("build binding 2: %v", err) } _, err = NewEvidencePlan([]FilterHoldBinding{b1, b2}) if err == nil { t.Error("incompatible same-mode triggers should be rejected for cross-mode merge") } }) t.Run("twoTerminalTriggersIncompatible", func(t *testing.T) { t1, err := NewFilterHoldRequirementTerminalGate("ch", textKinds, EventKindTerminal) if err != nil { t.Fatalf("build terminal 1: %v", err) } t2, err := NewFilterHoldRequirementTerminalGate("ch", textKinds, EventKindProviderError) if err != nil { t.Fatalf("build terminal 2: %v", err) } b1, err := NewFilterHoldBinding("f1", t1, true) if err != nil { t.Fatalf("build binding 1: %v", err) } b2, err := NewFilterHoldBinding("f2", t2, true) if err != nil { t.Fatalf("build binding 2: %v", err) } _, err = NewEvidencePlan([]FilterHoldBinding{b1, b2}) if err == nil { t.Error("different terminal triggers must be rejected") } }) t.Run("sameTerminalTriggerAccepted", func(t *testing.T) { t1, err := NewFilterHoldRequirementTerminalGate("ch", textKinds, EventKindTerminal) if err != nil { t.Fatalf("build terminal 1: %v", err) } t2, err := NewFilterHoldRequirementTerminalGateWithMaxBuffer("ch", textKinds, EventKindTerminal, 8192) if err != nil { t.Fatalf("build terminal 2: %v", err) } b1, err := NewFilterHoldBinding("f1", t1, true) if err != nil { t.Fatalf("build binding 1: %v", err) } b2, err := NewFilterHoldBinding("f2", t2, true) if err != nil { t.Fatalf("build binding 2: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{b1, b2}) if err != nil { t.Fatalf("NewEvidencePlan: %v", err) } req, _ := plan.BlockingRequirement("ch") if req.MaxBufferRunes() != 4096 { // min(8192, default) t.Errorf("min hard bound want 4096, got %d", req.MaxBufferRunes()) } }) t.Run("rollingMixedMinMaxBound", func(t *testing.T) { r1, err := NewFilterHoldRequirementRollingWithMaxBuffer("ch", textKinds, 200, 3000) if err != nil { t.Fatalf("build rolling 1: %v", err) } r2, err := NewFilterHoldRequirementRollingWithMaxBuffer("ch", textKinds, 400, 2000) if err != nil { t.Fatalf("build rolling 2: %v", err) } b1, err := NewFilterHoldBinding("f1", r1, true) if err != nil { t.Fatalf("build binding 1: %v", err) } b2, err := NewFilterHoldBinding("f2", r2, true) if err != nil { t.Fatalf("build binding 2: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{b1, b2}) if err != nil { t.Fatalf("NewEvidencePlan: %v", err) } req, _ := plan.BlockingRequirement("ch") if req.EvidenceRunes() != 400 { t.Errorf("evidenceRunes want max(200,400)=400, got %d", req.EvidenceRunes()) } if req.MaxBufferRunes() != 2000 { t.Errorf("maxBufferRunes want min(3000,2000)=2000, got %d", req.MaxBufferRunes()) } }) } // TestEvidenceTailSeparatesBlockingAndObserveSubscriptions verifies that // observe-only event kinds pass through without contributing to pending state // or creating ready epochs for the blocking path, while blocking kinds // contribute to pending as expected. func TestEvidenceTailSeparatesBlockingAndObserveSubscriptions(t *testing.T) { textKinds := []EventKind{EventKindTextDelta, EventKindReasoningDelta} toolKinds := []EventKind{EventKindToolCallFragment} t.Run("observeOnlyEventDoesNotCreatePending", func(t *testing.T) { // Non-blocking rolling (observe-only) + blocking fragment_gate. // textDelta/reasoningDelta from the non-blocking binding are // observe-only; toolCallFragment from the blocking binding is // blocking. r, err := NewFilterHoldRequirementRolling("ch", textKinds, 5) if err != nil { t.Fatalf("build rolling: %v", err) } f, err := NewFilterHoldRequirementFragmentGate("ch", toolKinds, EventKindToolCallFragment) if err != nil { t.Fatalf("build fragment: %v", err) } b1, err := NewFilterHoldBinding("f1", r, false) // blocksRelease=false → observe-only if err != nil { t.Fatalf("build binding 1: %v", err) } b2, err := NewFilterHoldBinding("f2", f, true) // blocking if err != nil { t.Fatalf("build binding 2: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{b1, b2}) if err != nil { t.Fatalf("NewEvidencePlan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("NewEvidenceTail: %v", err) } // reasoningDelta is observe-only: should pass through without state. ev, err := NewReasoningDeltaEvent("ch", "hello", now) if err != nil { t.Fatalf("build event: %v", err) } _, signal, err := tail.Append(ev) if err != nil { t.Fatalf("append observe: %v", err) } if signal != EvidenceTailSignalNone { t.Errorf("observe-only signal want none, got %s", signal) } if tail.channelState["ch"] != nil { t.Error("observe-only event must not create channel state") } }) t.Run("blockingEventContributesToPending", func(t *testing.T) { r, err := NewFilterHoldRequirementRolling("ch", textKinds, 5) if err != nil { t.Fatalf("build rolling: %v", err) } bind, err := NewFilterHoldBinding("f", r, true) if err != nil { t.Fatalf("build binding: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{bind}) if err != nil { t.Fatalf("NewEvidencePlan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("NewEvidenceTail: %v", err) } // textDelta is the sole blocking kind: should create channel state // and contribute to pending runes. ev, err := NewTextDeltaEvent("ch", "hello", now) if err != nil { t.Fatalf("build event: %v", err) } _, _, err = tail.Append(ev) if err != nil { t.Fatalf("append blocking: %v", err) } cs := tail.channelState["ch"] if cs == nil { t.Fatal("blocking event must create channel state") } if cs.pendingRunes != 5 { t.Errorf("pendingRunes want 5, got %d", cs.pendingRunes) } }) } // TestEvidenceTailBindsOnlyReadyEpochSnapshots verifies via a public-API table // that non-ready epochs (sub-threshold, unsubscribed, overflow) cannot be // prepared, and that post-snapshot append does not expand the snapshot. func TestEvidenceTailBindsOnlyReadyEpochSnapshots(t *testing.T) { textKinds := []EventKind{EventKindTextDelta} t.Run("subThresholdEpochNoRelease", func(t *testing.T) { // Sub-threshold: evidence_runes=100 but only 10 runes added. req, err := NewFilterHoldRequirementRolling("ch", textKinds, 100) if err != nil { t.Fatalf("build req: %v", err) } bind, err := NewFilterHoldBinding("f", req, true) if err != nil { t.Fatalf("build bind: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{bind}) if err != nil { t.Fatalf("plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("tail: %v", err) } // Append 1 event. No threshold → no epoch. ev, _ := NewTextDeltaEvent("ch", repeatRune('a', 10), now) tail.Append(ev) // No epochs exist. for id := range tail.epochs { t.Errorf("unexpected epoch %d for sub-threshold", id) } }) t.Run("unsubscribedEpochNoRelease", func(t *testing.T) { req, err := NewFilterHoldRequirementRolling("ch", textKinds, 3) if err != nil { t.Fatalf("build req: %v", err) } bind, err := NewFilterHoldBinding("f", req, true) if err != nil { t.Fatalf("build bind: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{bind}) if err != nil { t.Fatalf("plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("tail: %v", err) } // reasoning_delta not subscribed. Append returns an epoch but the // channel state must not be created and the event must not contribute // to pending runes. ev, _ := NewReasoningDeltaEvent("ch", "unsub", now) _, signal, _ := tail.Append(ev) if signal != EvidenceTailSignalNone { t.Errorf("unsubscribed signal want none, got %s", signal) } if tail.channelState["ch"] != nil { t.Errorf("unsubscribed event must not create channel state, got pendingRunes=%d", tail.channelState["ch"].pendingRunes) } }) t.Run("postSnapshotAppendExcluded", func(t *testing.T) { req, err := NewFilterHoldRequirementRollingWithMaxBuffer("ch", textKinds, 30, 4096) if err != nil { t.Fatalf("build req: %v", err) } bind, err := NewFilterHoldBinding("f", req, true) if err != nil { t.Fatalf("build bind: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{bind}) if err != nil { t.Fatalf("plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("tail: %v", err) } // Fill to threshold (3 events x 10 runes = 30). for i := 0; i < 3; i++ { ev, _ := NewTextDeltaEvent("ch", repeatRune('a', 10), now) tail.Append(ev) } // Find threshold epoch. var ep uint64 for _, r := range tail.epochs { if r.epochID > ep { ep = r.epochID } } pr, err := tail.PrepareRelease(ep) if err != nil { t.Fatalf("prepare: %v", err) } if len(pr.ReleaseEvents()) != 3 { t.Fatalf("snapshot want 3 events, got %d", len(pr.ReleaseEvents())) } // Zero-confirm to clear the prepared token. tail.ConfirmRelease(pr.Token(), ReleaseConfirmation{ReleasedEvents: 0}) // Append after prepare. evExtra, _ := NewTextDeltaEvent("ch", repeatRune('z', 10), now) tail.Append(evExtra) // Re-prepare same epoch — snapshot should still be 3, not 4. pr2, err := tail.PrepareRelease(ep) if err != nil { t.Fatalf("re-prepare: %v", err) } if len(pr2.ReleaseEvents()) != 3 { t.Errorf("re-prepared snapshot want 3, got %d", len(pr2.ReleaseEvents())) } }) t.Run("overflowEpochCannotPrepare", func(t *testing.T) { req, err := NewFilterHoldRequirementRollingWithMaxBuffer("ch", textKinds, 3, 10) if err != nil { t.Fatalf("build req: %v", err) } bind, err := NewFilterHoldBinding("f", req, true) if err != nil { t.Fatalf("build bind: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{bind}) if err != nil { t.Fatalf("plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("tail: %v", err) } // Fill to 10. for i := 0; i < 10; i++ { ev, _ := NewTextDeltaEvent("ch", "x", now) tail.Append(ev) } // 11th causes overflow. Append returns an overflow signal. ev11, _ := NewTextDeltaEvent("ch", "x", now) beforeOverflow := len(tail.epochs) _, signal, _ := tail.Append(ev11) if signal != EvidenceTailSignalBufferOverflow { t.Errorf("signal want overflow, got %s", signal) } // Overflow must not create any new epoch record. if len(tail.epochs) != beforeOverflow { t.Errorf("overflow must not create epoch record, before=%d after=%d", beforeOverflow, len(tail.epochs)) } }) } // TestEvidenceTailRepreparesOnlyUnconfirmedSnapshotSuffix verifies that after // zero or partial confirm, re-preparing the same epoch returns only the // unconfirmed suffix of the original snapshot. New appends after prepare are // excluded from the re-prepared snapshot. func TestEvidenceTailRepreparesOnlyUnconfirmedSnapshotSuffix(t *testing.T) { textKinds := []EventKind{EventKindTextDelta} t.Run("zeroConfirmReprepareReturnsFull", func(t *testing.T) { req, err := NewFilterHoldRequirementRollingWithMaxBuffer("ch", textKinds, 30, 4096) if err != nil { t.Fatalf("build req: %v", err) } bind, err := NewFilterHoldBinding("f", req, true) if err != nil { t.Fatalf("build bind: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{bind}) if err != nil { t.Fatalf("plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("tail: %v", err) } for i := 0; i < 3; i++ { ev, err := NewTextDeltaEvent("ch", fmt.Sprintf("ev-%d-pad-%s", i, repeatRune('b', 8)), now) if err != nil { t.Fatalf("new text delta event %d: %v", i, err) } if _, sig, err := tail.Append(ev); err != nil { t.Fatalf("append text event %d: %v", i, err) } else if sig != EvidenceTailSignalNone && sig != EvidenceTailSignalThreshold { t.Errorf("append text event %d signal want none or threshold, got %s", i, sig) } } var ep uint64 for _, r := range tail.epochs { if r.epochID > ep { ep = r.epochID } } pr, err := tail.PrepareRelease(ep) if err != nil { t.Fatalf("prepare release: %v", err) } // Zero confirm. if err := tail.ConfirmRelease(pr.Token(), ReleaseConfirmation{ReleasedEvents: 0}); err != nil { t.Fatalf("zero confirm: %v", err) } // Re-prepare: should get same 3 events. pr2, err := tail.PrepareRelease(ep) if err != nil { t.Fatalf("re-prepare after zero confirm: %v", err) } if len(pr2.ReleaseEvents()) != 3 { t.Errorf("re-prepared want 3 events, got %d", len(pr2.ReleaseEvents())) } // Exact content check with distinct payloads. for i, re := range pr2.ReleaseEvents() { text, err := re.AsTextDelta() if err != nil { t.Fatalf("as text delta %d: %v", i, err) } want := fmt.Sprintf("ev-%d-pad-%s", i, repeatRune('b', 8)) if text != want { t.Errorf("re-prepared[%d] text want %q, got %q", i, want, text) } } }) t.Run("partialConfirmReprepareReturnsSuffix", func(t *testing.T) { req, err := NewFilterHoldRequirementRollingWithMaxBuffer("ch", textKinds, 30, 4096) if err != nil { t.Fatalf("build req: %v", err) } bind, err := NewFilterHoldBinding("f", req, true) if err != nil { t.Fatalf("build bind: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{bind}) if err != nil { t.Fatalf("plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("tail: %v", err) } for i := 0; i < 4; i++ { ev, err := NewTextDeltaEvent("ch", fmt.Sprintf("ev-%d-pad-%s", i, repeatRune('x', 8)), now) if err != nil { t.Fatalf("new text delta event %d: %v", i, err) } if _, sig, err := tail.Append(ev); err != nil { t.Fatalf("append text event %d: %v", i, err) } else if sig != EvidenceTailSignalNone && sig != EvidenceTailSignalThreshold { t.Errorf("append text event %d signal want none or threshold, got %s", i, sig) } } var ep uint64 for _, r := range tail.epochs { if r.epochID > ep { ep = r.epochID } } pr, err := tail.PrepareRelease(ep) if err != nil { t.Fatalf("prepare release: %v", err) } if len(pr.ReleaseEvents()) != 4 { t.Fatalf("initial snapshot want 4, got %d", len(pr.ReleaseEvents())) } // Partial confirm 2. err = tail.ConfirmRelease(pr.Token(), ReleaseConfirmation{ReleasedEvents: 2}) if err != nil { t.Fatalf("partial confirm 2: %v", err) } // Re-prepare: should get remaining 2 events. pr2, err := tail.PrepareRelease(ep) if err != nil { t.Fatalf("re-prepare after partial: %v", err) } if len(pr2.ReleaseEvents()) != 2 { t.Errorf("re-prepared suffix want 2, got %d", len(pr2.ReleaseEvents())) } // Exact event content verification: remaining events must be entries [2,3]. for i, re := range pr2.ReleaseEvents() { text, err := re.AsTextDelta() if err != nil { t.Fatalf("as text delta %d: %v", i, err) } want := fmt.Sprintf("ev-%d-pad-%s", i+2, repeatRune('x', 8)) if text != want { t.Errorf("re-prepared[%d] text want %q, got %q", i, want, text) } } // Second full confirm must succeed on the re-prepared suffix. err = tail.ConfirmRelease(pr2.Token(), ReleaseConfirmation{ReleasedEvents: 2}) if err != nil { t.Fatalf("second full confirm on re-prepared suffix: %v", err) } // Epoch must now be fully consumed. epochRec, ok := tail.epochs[ep] if !ok { t.Fatal("epoch must exist after second confirm") } if !epochRec.consumed { t.Error("epoch must be consumed after full second confirm") } if epochRec.confirmedCount != 4 { t.Errorf("confirmedCount want 4, got %d", epochRec.confirmedCount) } // Cursor must reflect all 4 confirmed events. if tail.CommittedCursor("ch") != 4 { t.Errorf("committedCursor want 4, got %d", tail.CommittedCursor("ch")) } // Post-snapshot append must remain in pending, not part of consumed epoch. evExtra, err := NewTextDeltaEvent("ch", fmt.Sprintf("extra-%d", 99), now) if err != nil { t.Fatalf("new extra text delta: %v", err) } if _, sig, err := tail.Append(evExtra); err != nil { t.Fatalf("append extra: %v", err) } else if sig != EvidenceTailSignalNone { t.Errorf("append extra signal want none, got %s", sig) } cs := tail.channelState["ch"] if len(cs.pendingEntries) != 1 { t.Errorf("post-append pending want 1, got %d", len(cs.pendingEntries)) } if cs.pendingEntries[0].event.Kind() != EventKindTextDelta { t.Error("post-append entry must be text delta") } }) t.Run("postPrepareAppendExcludedFromReprepare", func(t *testing.T) { req, err := NewFilterHoldRequirementRollingWithMaxBuffer("ch", textKinds, 30, 4096) if err != nil { t.Fatalf("build req: %v", err) } bind, err := NewFilterHoldBinding("f", req, true) if err != nil { t.Fatalf("build bind: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{bind}) if err != nil { t.Fatalf("plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("tail: %v", err) } for i := 0; i < 3; i++ { ev, err := NewTextDeltaEvent("ch", fmt.Sprintf("ev-%d-pad-%s", i, repeatRune('c', 8)), now) if err != nil { t.Fatalf("new text delta event %d: %v", i, err) } if _, sig, err := tail.Append(ev); err != nil { t.Fatalf("append text event %d: %v", i, err) } else if sig != EvidenceTailSignalNone && sig != EvidenceTailSignalThreshold { t.Errorf("append text event %d signal want none or threshold, got %s", i, sig) } } var ep uint64 for _, r := range tail.epochs { if r.epochID > ep { ep = r.epochID } } pr, err := tail.PrepareRelease(ep) if err != nil { t.Fatalf("prepare release: %v", err) } if err := tail.ConfirmRelease(pr.Token(), ReleaseConfirmation{ReleasedEvents: 0}); err != nil { t.Fatalf("zero confirm: %v", err) } // Append after prepare. evExtra, err := NewTextDeltaEvent("ch", fmt.Sprintf("extra-%d-pad-%s", 99, repeatRune('d', 8)), now) if err != nil { t.Fatalf("new extra text delta: %v", err) } if _, sig, err := tail.Append(evExtra); err != nil { t.Fatalf("append extra: %v", err) } else if sig != EvidenceTailSignalNone && sig != EvidenceTailSignalThreshold { t.Errorf("append extra signal want none or threshold, got %s", sig) } // Re-prepare: should still be 3, not 4. pr2, err := tail.PrepareRelease(ep) if err != nil { t.Fatalf("re-prepare: %v", err) } if len(pr2.ReleaseEvents()) != 3 { t.Errorf("re-prepared want 3 (post-append excluded), got %d", len(pr2.ReleaseEvents())) } // Exact content check: first three payloads in original order. originalPayloads := []string{ fmt.Sprintf("ev-0-pad-%s", repeatRune('c', 8)), fmt.Sprintf("ev-1-pad-%s", repeatRune('c', 8)), fmt.Sprintf("ev-2-pad-%s", repeatRune('c', 8)), } for i, re := range pr2.ReleaseEvents() { text, err := re.AsTextDelta() if err != nil { t.Fatalf("as text delta %d: %v", i, err) } if text != originalPayloads[i] { t.Errorf("re-prepared[%d] want %q, got %q", i, originalPayloads[i], text) } } // Extra payload must NOT appear in re-prepared result. for _, re := range pr2.ReleaseEvents() { text, err := re.AsTextDelta() if err != nil { t.Fatalf("as text delta extra check: %v", err) } if strings.Contains(text, "extra-") { t.Errorf("extra payload must not appear in re-prepared result, got %q", text) } } }) } // TestEvidenceTailFragmentCompletionCreatesSafePrefixEpoch verifies that // CompleteFragment returns (epoch, signal, true, nil) with a contiguous // completed safe-prefix epoch, that interleaved A/B completion works, // that duplicate/unknown completion returns zero epoch, and that // over-confirm (confirming more than the snapshot) is rejected. func TestEvidenceTailFragmentCompletionCreatesSafePrefixEpoch(t *testing.T) { toolKinds := []EventKind{EventKindToolCallFragment} req, err := NewFilterHoldRequirementFragmentGate("ch", toolKinds, EventKindToolCallFragment) if err != nil { t.Fatalf("build req: %v", err) } bind, err := NewFilterHoldBinding("f", req, true) if err != nil { t.Fatalf("build bind: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{bind}) if err != nil { t.Fatalf("plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("tail: %v", err) } t.Run("interleavedABCompletion", func(t *testing.T) { // Interleave A and B with three fragments: A1, B1, A2. evA1, err := NewToolCallFragmentEvent("ch", "a1", "fn", `{"x":1}`, now) if err != nil { t.Fatalf("new A1 fragment: %v", err) } if _, _, err := tail.Append(evA1); err != nil { t.Fatalf("append A1: %v", err) } evB1, err := NewToolCallFragmentEvent("ch", "b1", "fn", `{"y":2}`, now) if err != nil { t.Fatalf("new B1 fragment: %v", err) } if _, _, err := tail.Append(evB1); err != nil { t.Fatalf("append B1: %v", err) } evA2, err := NewToolCallFragmentEvent("ch", "a2", "fn", `{"x":3}`, now) if err != nil { t.Fatalf("new A2 fragment: %v", err) } if _, _, err := tail.Append(evA2); err != nil { t.Fatalf("append A2: %v", err) } // Complete A (a1+a2): safe prefix = [A1] (B1 at index 1 is incomplete). epA, sigA, okA, err := tail.CompleteFragment("ch", "a1") if err != nil { t.Fatalf("complete A: %v", err) } if !okA { t.Error("complete A should return true") } if sigA != EvidenceTailSignalTrigger { t.Errorf("signal want trigger, got %s", sigA) } if epA.ID() == 0 { t.Fatal("complete A should produce an epoch") } prA, err := tail.PrepareRelease(epA.ID()) if err != nil { t.Fatalf("prepare A release: %v", err) } if len(prA.ReleaseEvents()) != 1 { t.Errorf("A safe prefix want 1 event, got %d", len(prA.ReleaseEvents())) } // Complete B (b1): now a1 and b1 are done, but a2 is still pending. // safe prefix = [A1, B1] (a2 at index 2 is incomplete). epB, sigB, okB, err := tail.CompleteFragment("ch", "b1") if err != nil { t.Fatalf("complete B: %v", err) } if !okB { t.Error("complete B should return true") } if sigB != EvidenceTailSignalTrigger { t.Errorf("signal want trigger, got %s", sigB) } prB, err := tail.PrepareRelease(epB.ID()) if err != nil { t.Fatalf("prepare B release: %v", err) } if len(prB.ReleaseEvents()) != 2 { t.Errorf("AB safe prefix (a2 incomplete) want 2 events, got %d", len(prB.ReleaseEvents())) } // Complete A again? No — a1 is already completed. Complete a2. epA2, _, okA2, err := tail.CompleteFragment("ch", "a2") if err != nil { t.Fatalf("complete A2: %v", err) } if !okA2 { t.Error("complete A2 should return true") } prA2, err := tail.PrepareRelease(epA2.ID()) if err != nil { t.Fatalf("prepare A2 release: %v", err) } if len(prA2.ReleaseEvents()) != 3 { t.Errorf("all complete want 3 events, got %d", len(prA2.ReleaseEvents())) } // Re-preparing A should fail (invalidated by later completion). _, err = tail.PrepareRelease(epA.ID()) if err == nil { t.Error("re-prepare invalidated epoch should fail") } }) t.Run("duplicateCompletionReturnsZero", func(t *testing.T) { ev, err := NewToolCallFragmentEvent("ch", "dup", "fn", `{}`, now) if err != nil { t.Fatalf("new dup fragment: %v", err) } if _, _, err := tail.Append(ev); err != nil { t.Fatalf("append dup: %v", err) } ep1, _, ok1, err := tail.CompleteFragment("ch", "dup") if err != nil { t.Fatalf("complete dup: %v", err) } if !ok1 || ep1.ID() == 0 { t.Fatal("first complete should succeed") } ep2, sig2, ok2, err := tail.CompleteFragment("ch", "dup") if err != nil { t.Fatalf("duplicate complete: %v", err) } if ok2 { t.Error("duplicate complete should return false") } if ep2.ID() != 0 { t.Error("duplicate complete should return zero epoch") } if sig2 != EvidenceTailSignalNone { t.Errorf("duplicate complete signal want none, got %s", sig2) } }) t.Run("unknownToolIdReturnsZero", func(t *testing.T) { ep, sig, ok, err := tail.CompleteFragment("ch", "nonexistent") if err != nil { t.Fatalf("complete nonexistent: %v", err) } if ok { t.Error("unknown ID should return false") } if ep.ID() != 0 { t.Error("unknown ID should return zero epoch") } if sig != EvidenceTailSignalNone { t.Errorf("unknown ID signal want none, got %s", sig) } }) } // TestEvidenceTailFragmentBoundNeverCreatesReleaseEpoch verifies that // exceeding the per-ID bound never creates a releaseable epoch or token, // even on the first overflow event (limit+1). func TestEvidenceTailFragmentBoundNeverCreatesReleaseEpoch(t *testing.T) { toolKinds := []EventKind{EventKindToolCallFragment} t.Run("exactBoundNoRelease", func(t *testing.T) { req, err := NewFilterHoldRequirementFragmentGateWithMaxBuffer("ch", toolKinds, EventKindToolCallFragment, 10) if err != nil { t.Fatalf("build req: %v", err) } bind, err := NewFilterHoldBinding("f", req, true) if err != nil { t.Fatalf("build bind: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{bind}) if err != nil { t.Fatalf("plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("tail: %v", err) } // 2 fragments of 5 runes each = exactly 10 (the bound). ev1, _ := NewToolCallFragmentEvent("ch", "id", "fn", "aaaaa", now) ev2, _ := NewToolCallFragmentEvent("ch", "id", "fn", "aaaaa", now) tail.Append(ev1) tail.Append(ev2) // Complete: safe prefix includes both entries = 10 runes. ep, _, ok, _ := tail.CompleteFragment("ch", "id") if !ok { t.Fatal("complete at exact bound should return true") } pr, err := tail.PrepareRelease(ep.ID()) if err != nil { t.Fatalf("prepare at exact bound: %v", err) } if len(pr.ReleaseEvents()) != 2 { t.Errorf("release want 2 events, got %d", len(pr.ReleaseEvents())) } }) t.Run("limitPlusOneNeverCreatesEpoch", func(t *testing.T) { // Bound = 10. 3 fragments of 4 runes = 12 > 10. The 3rd should // overflow and not be added to pending state. req, err := NewFilterHoldRequirementFragmentGateWithMaxBuffer("ch", toolKinds, EventKindToolCallFragment, 10) if err != nil { t.Fatalf("build req: %v", err) } bind, err := NewFilterHoldBinding("f", req, true) if err != nil { t.Fatalf("build bind: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{bind}) if err != nil { t.Fatalf("plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("tail: %v", err) } ev1, _ := NewToolCallFragmentEvent("ch", "id", "fn", "abcd", now) ev2, _ := NewToolCallFragmentEvent("ch", "id", "fn", "abcd", now) tail.Append(ev1) tail.Append(ev2) // 3rd fragment overflows — signal is overflow, state unchanged. ev3, _ := NewToolCallFragmentEvent("ch", "id", "fn", "abcd", now) _, signal, _ := tail.Append(ev3) if signal != EvidenceTailSignalBufferOverflow { t.Errorf("signal want overflow, got %s", signal) } // Verify the overflow did not add a 3rd pending entry. cs := tail.channelState["ch"] if cs == nil { t.Fatal("channel state missing") } if len(cs.pendingEntries) != 2 { t.Errorf("pendingEntries want 2 (overflow rejected), got %d", len(cs.pendingEntries)) } // The fragment state for "id" still has 2 entries (the overflow was rejected). frag := cs.fragmentState["id"] if frag == nil { t.Fatal("fragment state missing") } if frag.runes != 8 { t.Errorf("fragment runes want 8, got %d", frag.runes) } // No non-invalidated overflow epoch should exist. for _, r := range tail.epochs { if !r.invalidated { t.Errorf("overflow must not create non-invalidated epoch, got ID=%d", r.epochID) } } }) } // TestEvidenceTailBoundsAndCopiesEffectiveLookBehind verifies that the // committed look-behind is trimmed to the channel's effective evidence_runes // (Unicode suffix), that the accessor returns a deep copy isolated from // internal mutation, and that replace/continuation behave correctly with // respect to cursor and look-behind preservation. func TestEvidenceTailBoundsAndCopiesEffectiveLookBehind(t *testing.T) { textKinds := []EventKind{EventKindTextDelta} t.Run("effectiveEvidenceRuneBound", func(t *testing.T) { // evidence_runes=10, maxBufferRunes=4096. Confirm enough events to // exceed 10 runes and verify lookBehind is trimmed to ≤10 runes. req, err := NewFilterHoldRequirementRollingWithMaxBuffer("ch", textKinds, 10, 4096) if err != nil { t.Fatalf("build req: %v", err) } bind, err := NewFilterHoldBinding("f", req, true) if err != nil { t.Fatalf("build bind: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{bind}) if err != nil { t.Fatalf("plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("tail: %v", err) } // Add 3 events of 5 runes each = 15 runes (exceeds 10). for i := 0; i < 3; i++ { ev, _ := NewTextDeltaEvent("ch", repeatRune('a', 5), now) tail.Append(ev) } var ep uint64 for _, r := range tail.epochs { if r.epochID > ep { ep = r.epochID } } pr, _ := tail.PrepareRelease(ep) tail.ConfirmRelease(pr.Token(), ReleaseConfirmation{ReleasedEvents: 3}) lb := tail.EffectiveLookBehind("ch") totalRunes := 0 for _, ev := range lb { totalRunes += runeCountForEvent(ev) } if totalRunes > 10 { t.Errorf("lookBehind runes %d exceeds effectiveEvidenceRunes 10", totalRunes) } if len(lb) == 0 { t.Error("lookBehind must not be empty") } }) t.Run("callerMutationIsolation", func(t *testing.T) { req, err := NewFilterHoldRequirementRollingWithMaxBuffer("ch", textKinds, 5, 4096) if err != nil { t.Fatalf("build req: %v", err) } bind, err := NewFilterHoldBinding("f", req, true) if err != nil { t.Fatalf("build bind: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{bind}) if err != nil { t.Fatalf("plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("tail: %v", err) } // 5 events of 1 rune each = 5 runes (threshold). for i := 0; i < 5; i++ { ev, _ := NewTextDeltaEvent("ch", "x", now) tail.Append(ev) } var ep uint64 for _, r := range tail.epochs { if r.epochID > ep { ep = r.epochID } } pr, _ := tail.PrepareRelease(ep) tail.ConfirmRelease(pr.Token(), ReleaseConfirmation{ReleasedEvents: 5}) lb := tail.EffectiveLookBehind("ch") orig := make([]NormalizedEvent, len(lb)) copy(orig, lb) // Mutate returned slice. lb[0] = NormalizedEvent{} // Second call should return a fresh copy. lb2 := tail.EffectiveLookBehind("ch") if len(lb2) != len(orig) { t.Errorf("lookBehind length changed: got %d, want %d", len(lb2), len(orig)) } for i := range lb2 { if lb2[i].Kind() != orig[i].Kind() { t.Errorf("lookBehind[%d] mutated: got %q, want %q", i, lb2[i].Kind(), orig[i].Kind()) } } }) t.Run("replaceClearsLookBehindAndCursor", func(t *testing.T) { req, err := NewFilterHoldRequirementRolling("ch", textKinds, 5) if err != nil { t.Fatalf("build req: %v", err) } bind, err := NewFilterHoldBinding("f", req, true) if err != nil { t.Fatalf("build bind: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{bind}) if err != nil { t.Fatalf("plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("tail: %v", err) } // 3 events of 1 rune each = 3 runes. Need 5 for threshold. for i := 0; i < 5; i++ { ev, _ := NewTextDeltaEvent("ch", "x", now) tail.Append(ev) } var ep uint64 for _, r := range tail.epochs { if r.epochID > ep { ep = r.epochID } } pr, _ := tail.PrepareRelease(ep) tail.ConfirmRelease(pr.Token(), ReleaseConfirmation{ReleasedEvents: 2}) if tail.CommittedCursor("ch") != 2 { t.Fatalf("cursor before replace want 2, got %d", tail.CommittedCursor("ch")) } if len(tail.EffectiveLookBehind("ch")) != 2 { t.Fatalf("lookBehind before replace want 2, got %d", len(tail.EffectiveLookBehind("ch"))) } tail.ResetForReplace() if tail.CommittedCursor("ch") != 0 { t.Errorf("cursor after replace want 0, got %d", tail.CommittedCursor("ch")) } if tail.EffectiveLookBehind("ch") != nil { t.Errorf("lookBehind after replace want nil, got %v", tail.EffectiveLookBehind("ch")) } }) t.Run("continuationPreservesCursorAndLookBehind", func(t *testing.T) { req, err := NewFilterHoldRequirementRolling("ch", textKinds, 5) if err != nil { t.Fatalf("build req: %v", err) } bind, err := NewFilterHoldBinding("f", req, true) if err != nil { t.Fatalf("build bind: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{bind}) if err != nil { t.Fatalf("plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("tail: %v", err) } // 3 events of 1 rune each — need 5 for threshold. for i := 0; i < 5; i++ { ev, _ := NewTextDeltaEvent("ch", "x", now) tail.Append(ev) } var ep uint64 for _, r := range tail.epochs { if r.epochID > ep { ep = r.epochID } } pr, _ := tail.PrepareRelease(ep) tail.ConfirmRelease(pr.Token(), ReleaseConfirmation{ReleasedEvents: 1}) tail.PrepareContinuation() if tail.CommittedCursor("ch") != 1 { t.Errorf("cursor after continuation want 1, got %d", tail.CommittedCursor("ch")) } if len(tail.EffectiveLookBehind("ch")) != 1 { t.Errorf("lookBehind after continuation want 1, got %d", len(tail.EffectiveLookBehind("ch"))) } }) } // TestEvidenceTailRejectsInvalidReleaseLifecycle verifies via a public API that // stale/invalidated/consumed tokens, overlapping prepared tokens, already- // prepared epochs, and out-of-bounds confirmations are all rejected with // deterministic errors (never panic). func TestEvidenceTailRejectsInvalidReleaseLifecycle(t *testing.T) { textKinds := []EventKind{EventKindTextDelta} t.Run("unknownEpochRejected", func(t *testing.T) { req, err := NewFilterHoldRequirementRolling("ch", textKinds, 5) if err != nil { t.Fatalf("build req: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{mustBind(t, req)}) if err != nil { t.Fatalf("plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("tail: %v", err) } // Non-existent epoch. _, err = tail.PrepareRelease(99999) if err == nil { t.Error("unknown epoch must be rejected") } }) t.Run("consumedEpochRejected", func(t *testing.T) { tail := buildThresholdTail(t, textKinds, 5) ep := findLatestEpoch(t, tail) pr, err := tail.PrepareRelease(ep) if err != nil { t.Fatalf("prepare: %v", err) } // Full confirm → epoch consumed. if err := tail.ConfirmRelease(pr.Token(), ReleaseConfirmation{ReleasedEvents: 3}); err != nil { t.Fatalf("confirm: %v", err) } // Re-prepare consumed epoch must fail. _, err = tail.PrepareRelease(ep) if err == nil { t.Error("consumed epoch must be rejected on re-prepare") } }) t.Run("invalidatedEpochRejected", func(t *testing.T) { tail := buildThresholdTail(t, textKinds, 5) ep := findLatestEpoch(t, tail) pr, err := tail.PrepareRelease(ep) if err != nil { t.Fatalf("prepare: %v", err) } // Continuation invalidates the epoch. tail.PrepareContinuation() // Confirm with invalidated token must fail (no panic). err = tail.ConfirmRelease(pr.Token(), ReleaseConfirmation{ReleasedEvents: 1}) if err == nil { t.Error("invalidated token must be rejected") } }) t.Run("duplicatePrepareRejected", func(t *testing.T) { tail := buildThresholdTail(t, textKinds, 5) ep := findLatestEpoch(t, tail) // First prepare. _, err := tail.PrepareRelease(ep) if err != nil { t.Fatalf("prepare 1: %v", err) } // Second prepare on same channel must fail (overlapping token). _, err = tail.PrepareRelease(ep) if err == nil { t.Error("duplicate prepare must be rejected") } }) t.Run("overConfirmationRejected", func(t *testing.T) { tail := buildThresholdTail(t, textKinds, 5) ep := findLatestEpoch(t, tail) pr, err := tail.PrepareRelease(ep) if err != nil { t.Fatalf("prepare: %v", err) } err = tail.ConfirmRelease(pr.Token(), ReleaseConfirmation{ReleasedEvents: 99}) if err == nil { t.Error("over-confirmation must be rejected") } }) t.Run("staleTokenAfterResetRejected", func(t *testing.T) { tail := buildThresholdTail(t, textKinds, 5) ep := findLatestEpoch(t, tail) pr, err := tail.PrepareRelease(ep) if err != nil { t.Fatalf("prepare: %v", err) } token := pr.Token() // ResetForReplace invalidates. tail.ResetForReplace() // Confirm with stale token must fail. err = tail.ConfirmRelease(token, ReleaseConfirmation{ReleasedEvents: 1}) if err == nil { t.Error("stale token after reset must be rejected") } }) t.Run("staleTokenAfterTerminalDiscardRejected", func(t *testing.T) { tail := buildThresholdTail(t, textKinds, 5) ep := findLatestEpoch(t, tail) pr, err := tail.PrepareRelease(ep) if err != nil { t.Fatalf("prepare: %v", err) } token := pr.Token() tail.DiscardPendingForTerminal() // Confirm with stale token must fail. err = tail.ConfirmRelease(token, ReleaseConfirmation{ReleasedEvents: 1}) if err == nil { t.Error("stale token after terminal discard must be rejected") } }) t.Run("staleTokenAfterContinuationRejected", func(t *testing.T) { tail := buildThresholdTail(t, textKinds, 5) ep := findLatestEpoch(t, tail) pr, err := tail.PrepareRelease(ep) if err != nil { t.Fatalf("prepare: %v", err) } token := pr.Token() tail.PrepareContinuation() // Confirm with stale token must fail. err = tail.ConfirmRelease(token, ReleaseConfirmation{ReleasedEvents: 1}) if err == nil { t.Error("stale token after continuation must be rejected") } }) t.Run("snapshotMismatchRejectsConfirm", func(t *testing.T) { req, err := NewFilterHoldRequirementRollingWithMaxBuffer("ch", textKinds, 30, 4096) if err != nil { t.Fatalf("build req: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{mustBind(t, req)}) if err != nil { t.Fatalf("plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("tail: %v", err) } // Fill to threshold (3 events). for i := 0; i < 3; i++ { ev, _ := NewTextDeltaEvent("ch", repeatRune('a', 10), now) tail.Append(ev) } ep := findLatestEpoch(t, tail) pr, err := tail.PrepareRelease(ep) if err != nil { t.Fatalf("prepare: %v", err) } // Zero confirm (does not clear preparedByChannel — token still valid). tail.ConfirmRelease(pr.Token(), ReleaseConfirmation{ReleasedEvents: 0}) // Append a new event after prepare. evExtra, _ := NewTextDeltaEvent("ch", repeatRune('z', 10), now) tail.Append(evExtra) // Re-prepare (zero confirm clears preparedByChannel, so prepare succeeds). _, err = tail.PrepareRelease(ep) if err != nil { t.Fatalf("re-prepare: %v", err) } // Confirm with old token. err = tail.ConfirmRelease(pr.Token(), ReleaseConfirmation{ReleasedEvents: 0}) if err == nil { t.Error("confirm with stale token after re-prepare must fail") } }) } // TestEvidenceTailRecoveryTransitions verifies that ResetForReplace, // PrepareContinuation, and DiscardPendingForTerminal all invalidate prepared // tokens with deterministic errors (no panic), clear prepared/preparedByChannel // state, and that subsequent append/prepare cycles work correctly after each // transition. func TestEvidenceTailRecoveryTransitions(t *testing.T) { textKinds := []EventKind{EventKindTextDelta} t.Run("ResetForReplaceInvalidatesAllTokens", func(t *testing.T) { tail := buildThresholdTail(t, textKinds, 5) ep := findLatestEpoch(t, tail) pr, err := tail.PrepareRelease(ep) if err != nil { t.Fatalf("prepare: %v", err) } token := pr.Token() tail.ResetForReplace() // Token must be invalid. err = tail.ConfirmRelease(token, ReleaseConfirmation{ReleasedEvents: 1}) if err == nil { t.Error("reset must invalidate token") } // After reset, state is cleared. New events and new prepare cycle should work. for i := 0; i < 5; i++ { ev, _ := NewTextDeltaEvent("ch", "x", now) tail.Append(ev) } newEp := findLatestEpoch(t, tail) pr2, err := tail.PrepareRelease(newEp) if err != nil { t.Fatalf("new prepare after reset: %v", err) } // New token must work. err = tail.ConfirmRelease(pr2.Token(), ReleaseConfirmation{ReleasedEvents: 3}) if err != nil { t.Fatalf("new confirm after reset: %v", err) } }) t.Run("PrepareContinuationInvalidatesTokensPreservesLookBehind", func(t *testing.T) { tail := buildThresholdTail(t, textKinds, 5) ep := findLatestEpoch(t, tail) pr, err := tail.PrepareRelease(ep) if err != nil { t.Fatalf("prepare: %v", err) } token := pr.Token() // Confirm partial to create look-behind. tail.ConfirmRelease(token, ReleaseConfirmation{ReleasedEvents: 1}) // Append pending event. ev, _ := NewTextDeltaEvent("ch", "y", now) tail.Append(ev) tail.PrepareContinuation() // Token must be invalid. err = tail.ConfirmRelease(token, ReleaseConfirmation{ReleasedEvents: 1}) if err == nil { t.Error("continuation must invalidate token") } // Look-behind must be preserved. lb := tail.EffectiveLookBehind("ch") if len(lb) != 1 { t.Errorf("lookBehind want 1 after continuation, got %d", len(lb)) } // Cursor must be preserved. if tail.CommittedCursor("ch") != 1 { t.Errorf("cursor want 1 after continuation, got %d", tail.CommittedCursor("ch")) } // Pending must be cleared. cs := tail.channelState["ch"] if len(cs.pendingEntries) != 0 { t.Errorf("pending must be cleared, got %d", len(cs.pendingEntries)) } }) t.Run("DiscardPendingForTerminalInvalidatesTokensPreservesLookBehind", func(t *testing.T) { tail := buildThresholdTail(t, textKinds, 5) ep := findLatestEpoch(t, tail) pr, err := tail.PrepareRelease(ep) if err != nil { t.Fatalf("prepare: %v", err) } token := pr.Token() // Confirm partial. tail.ConfirmRelease(token, ReleaseConfirmation{ReleasedEvents: 1}) // Append pending. ev, _ := NewTextDeltaEvent("ch", "z", now) tail.Append(ev) tail.DiscardPendingForTerminal() // Token must be invalid. err = tail.ConfirmRelease(token, ReleaseConfirmation{ReleasedEvents: 1}) if err == nil { t.Error("terminal discard must invalidate token") } // Look-behind preserved. lb := tail.EffectiveLookBehind("ch") if len(lb) != 1 { t.Errorf("lookBehind want 1, got %d", len(lb)) } // Pending cleared. cs := tail.channelState["ch"] if len(cs.pendingEntries) != 0 { t.Errorf("pending must be cleared, got %d", len(cs.pendingEntries)) } }) t.Run("recoveryTransitionsNoPanicOnConfirm", func(t *testing.T) { // Verify that outstanding tokens after each recovery transition // always return an error (not panic) regardless of token value. transitions := []func(t *EvidenceTail){ func(t *EvidenceTail) { t.ResetForReplace() }, func(t *EvidenceTail) { t.PrepareContinuation() }, func(t *EvidenceTail) { t.DiscardPendingForTerminal() }, } for _, tr := range transitions { tail := buildThresholdTail(t, textKinds, 5) ep := findLatestEpoch(t, tail) pr, err := tail.PrepareRelease(ep) if err != nil { t.Fatalf("prepare: %v", err) } token := pr.Token() tr(tail) // Must not panic; must return error. err = tail.ConfirmRelease(token, ReleaseConfirmation{ReleasedEvents: 1}) if err == nil { t.Errorf("transition must invalidate token") } } }) t.Run("invalidatedEpochHasClearedRecordFields", func(t *testing.T) { tail := buildThresholdTail(t, textKinds, 5) ep := findLatestEpoch(t, tail) _, err := tail.PrepareRelease(ep) if err != nil { t.Fatalf("prepare: %v", err) } tail.PrepareContinuation() // Find the record and verify fields are cleared. for _, rec := range tail.epochs { if rec.epochID == ep { if !rec.invalidated { t.Error("epoch must be invalidated") } if rec.prepared { t.Error("prepared must be false after invalidation") } if rec.token != "" { t.Error("token must be empty after invalidation") } if rec.snapshotSize != 0 { t.Errorf("snapshotSize must be 0 after invalidation, got %d", rec.snapshotSize) } } } }) } // TestEvidenceTailFragmentCompletionPreservesOtherChannels verifies that // CompleteFragment invalidates only the same channel's superseded epochs and // prepared tokens, while leaving unrelated channel outstanding tokens intact. // A rolling/text channel with a prepared release token must still be able to // confirm normally after a fragment/tool channel CompleteFragment invalidates // its own channel's state. func TestEvidenceTailFragmentCompletionPreservesOtherChannels(t *testing.T) { textKinds := []EventKind{EventKindTextDelta} toolKinds := []EventKind{EventKindToolCallFragment} // Build a plan with two channels: "text" (rolling) and "tool" (fragment_gate). textReq, err := NewFilterHoldRequirementRollingWithMaxBuffer("text", textKinds, 15, 4096) if err != nil { t.Fatalf("build text req: %v", err) } toolReq, err := NewFilterHoldRequirementFragmentGate("tool", toolKinds, EventKindToolCallFragment) if err != nil { t.Fatalf("build tool req: %v", err) } tool2Req, err := NewFilterHoldRequirementFragmentGate("tool2", toolKinds, EventKindToolCallFragment) if err != nil { t.Fatalf("build tool2 req: %v", err) } interleaveReq, err := NewFilterHoldRequirementFragmentGate("interleave", toolKinds, EventKindToolCallFragment) if err != nil { t.Fatalf("build interleave req: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{ mustBind(t, textReq), mustBind(t, toolReq), mustBind(t, tool2Req), mustBind(t, interleaveReq), }) if err != nil { t.Fatalf("plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("tail: %v", err) } t.Run("fragmentCompletionDoesNotBreakRollingTokenConfirm", func(t *testing.T) { // Prepare a rolling/text token first. for i := 0; i < 3; i++ { ev, err := NewTextDeltaEvent("text", fmt.Sprintf("text-%d", i), now) if err != nil { t.Fatalf("new text delta event %d: %v", i, err) } if _, _, err := tail.Append(ev); err != nil { t.Fatalf("append text event %d: %v", i, err) } } var textEp uint64 for _, r := range tail.epochs { if r.channel == "text" && r.epochID > textEp { textEp = r.epochID } } textPR, err := tail.PrepareRelease(textEp) if err != nil { t.Fatalf("prepare text release: %v", err) } if len(textPR.ReleaseEvents()) != 3 { t.Fatalf("text snapshot want 3 events, got %d", len(textPR.ReleaseEvents())) } // Now do fragment operations on the "tool" channel. evA1, err := NewToolCallFragmentEvent("tool", "a1", "fn", `{"x":1}`, now) if err != nil { t.Fatalf("new tool fragment A1: %v", err) } if _, _, err := tail.Append(evA1); err != nil { t.Fatalf("append tool A1: %v", err) } evA2, err := NewToolCallFragmentEvent("tool", "a1", "fn", `{"x":2}`, now) if err != nil { t.Fatalf("new tool fragment A2: %v", err) } if _, _, err := tail.Append(evA2); err != nil { t.Fatalf("append tool A2: %v", err) } // Complete fragment "a1" on tool channel. _, _, toolOk, err := tail.CompleteFragment("tool", "a1") if err != nil { t.Fatalf("complete fragment tool: %v", err) } if !toolOk { t.Fatal("fragment completion should return true") } // Verify the text channel token is still valid. err = tail.ConfirmRelease(textPR.Token(), ReleaseConfirmation{ReleasedEvents: 3}) if err != nil { t.Fatalf("confirm text release after tool fragment completion: %v", err) } // Text cursor must be 3. if tail.CommittedCursor("text") != 3 { t.Errorf("text cursor want 3, got %d", tail.CommittedCursor("text")) } // Verify the tool fragment epoch was created by CompleteFragment. toolEpochCreated := false for _, r := range tail.epochs { if r.channel == "tool" && r.epochID > 0 { toolEpochCreated = true break } } if !toolEpochCreated { t.Error("tool fragment completion should create an epoch") } }) t.Run("sameChannelOldTokenRejected", func(t *testing.T) { // Prepare a tool channel token, then complete fragment should invalidate it. evT1, err := NewToolCallFragmentEvent("tool2", "t1", "fn", `"a"`, now) if err != nil { t.Fatalf("new tool fragment T1: %v", err) } if _, _, err := tail.Append(evT1); err != nil { t.Fatalf("append tool2 T1: %v", err) } evT2, err := NewToolCallFragmentEvent("tool2", "t1", "fn", `"b"`, now) if err != nil { t.Fatalf("new tool fragment T2: %v", err) } if _, _, err := tail.Append(evT2); err != nil { t.Fatalf("append tool2 T2: %v", err) } // Complete t1 -> creates safe prefix epoch for tool2. _, _, ok, err := tail.CompleteFragment("tool2", "t1") if err != nil { t.Fatalf("complete tool2 t1: %v", err) } if !ok { t.Fatal("first complete should succeed") } // Prepare the safe prefix token. var toolEp uint64 for _, r := range tail.epochs { if r.channel == "tool2" && r.epochID > toolEp { toolEp = r.epochID } } toolPR, err := tail.PrepareRelease(toolEp) if err != nil { t.Fatalf("prepare tool2 release: %v", err) } oldToken := toolPR.Token() if oldToken == "" { t.Fatal("prepared token must not be empty") } // Add another fragment and complete it -> new safe prefix -> invalidates old. evT3, err := NewToolCallFragmentEvent("tool2", "t2", "fn", `"c"`, now) if err != nil { t.Fatalf("new tool call fragment: %v", err) } if _, _, err := tail.Append(evT3); err != nil { t.Fatalf("append tool2 fragment: %v", err) } _, _, _, err = tail.CompleteFragment("tool2", "t2") if err != nil { t.Fatalf("complete fragment tool2 t2: %v", err) } // Old token must now be stale. err = tail.ConfirmRelease(oldToken, ReleaseConfirmation{ReleasedEvents: 2}) if err == nil { t.Error("old tool token after superseding completion must be rejected") } }) t.Run("interleavedFragmentABExpandsSafePrefixAfterConfirm", func(t *testing.T) { // A1, B1, A2 interleaved with toolCallID "a"/"b"/"a". // Complete A → safe=[A1] (B1 incomplete at index 1). Partial confirm 1. // Complete B → all fragments done, safe=[B1,A2] (remaining pending). Full confirm 2. evA1, err := NewToolCallFragmentEvent("interleave", "a", "fn", `"a1"`, now) if err != nil { t.Fatalf("new A1 fragment: %v", err) } if _, sig, err := tail.Append(evA1); err != nil { t.Fatalf("append A1: %v", err) } else if sig != EvidenceTailSignalNone { t.Errorf("append A1 signal want none, got %s", sig) } evB1, err := NewToolCallFragmentEvent("interleave", "b", "fn", `"b1"`, now) if err != nil { t.Fatalf("new B1 fragment: %v", err) } if _, _, err := tail.Append(evB1); err != nil { t.Fatalf("append B1: %v", err) } evA2, err := NewToolCallFragmentEvent("interleave", "a", "fn", `"a2"`, now) if err != nil { t.Fatalf("new A2 fragment: %v", err) } if _, _, err := tail.Append(evA2); err != nil { t.Fatalf("append A2: %v", err) } // Complete A → safe prefix [A1] (B1 incomplete at index 1). epA, _, ok, err := tail.CompleteFragment("interleave", "a") if err != nil { t.Fatalf("complete A: %v", err) } if !ok { t.Fatal("complete A should succeed") } prA, err := tail.PrepareRelease(epA.ID()) if err != nil { t.Fatalf("prepare A release: %v", err) } if len(prA.ReleaseEvents()) != 1 { t.Fatalf("A safe prefix want 1 event, got %d", len(prA.ReleaseEvents())) } // Verify exact content of A1. reA1, err := prA.ReleaseEvents()[0].AsToolCallFragment() if err != nil { t.Fatalf("as tool call fragment [0]: %v", err) } if reA1.ID != "a" || reA1.Name != "fn" || reA1.Arguments != `"a1"` { t.Errorf("A1 fragment want id=a name=fn args=\"a1\", got id=%q name=%q args=%q", reA1.ID, reA1.Name, reA1.Arguments) } err = tail.ConfirmRelease(prA.Token(), ReleaseConfirmation{ReleasedEvents: 1}) if err != nil { t.Fatalf("partial confirm A: %v", err) } // Complete B → all fragments completed, safe prefix = remaining [B1,A2]. epB, _, ok, err := tail.CompleteFragment("interleave", "b") if err != nil { t.Fatalf("complete B: %v", err) } if !ok { t.Fatal("complete B should succeed") } prB, err := tail.PrepareRelease(epB.ID()) if err != nil { t.Fatalf("prepare B release: %v", err) } if len(prB.ReleaseEvents()) != 2 { t.Fatalf("remaining safe prefix want 2 events [B1,A2], got %d", len(prB.ReleaseEvents())) } // Exact public content and order: [B1, A2]. for i := 0; i < 2; i++ { re, err := prB.ReleaseEvents()[i].AsToolCallFragment() if err != nil { t.Fatalf("as tool call fragment[%d]: %v", i, err) } var wantID, wantArgs string if i == 0 { wantID, wantArgs = "b", `"b1"` } else { wantID, wantArgs = "a", `"a2"` } if re.ID != wantID { t.Errorf("release[%d] ID want %q, got %q", i, wantID, re.ID) } if re.Arguments != wantArgs { t.Errorf("release[%d] arguments want %q, got %q", i, wantArgs, re.Arguments) } } // Genuine partial confirm: ReleasedEvents=1 on a 2-event snapshot. err = tail.ConfirmRelease(prB.Token(), ReleaseConfirmation{ReleasedEvents: 1}) if err != nil { t.Fatalf("partial confirm 1 of 2: %v", err) } // Re-prepare: should return only the unconfirmed suffix [A2]. prB2, err := tail.PrepareRelease(epB.ID()) if err != nil { t.Fatalf("re-prepare after partial: %v", err) } if len(prB2.ReleaseEvents()) != 1 { t.Fatalf("re-prepared suffix want 1 event [A2], got %d", len(prB2.ReleaseEvents())) } reSuffix, err := prB2.ReleaseEvents()[0].AsToolCallFragment() if err != nil { t.Fatalf("as tool call fragment suffix: %v", err) } if reSuffix.ID != "a" || reSuffix.Arguments != `"a2"` { t.Errorf("suffix event want ID=a args=\"a2\", got ID=%q args=%q", reSuffix.ID, reSuffix.Arguments) } // Fragment "a" is already completed from the first CompleteFragment // call, so no further completion is needed. Full confirm the remaining // re-prepared suffix. err = tail.ConfirmRelease(prB2.Token(), ReleaseConfirmation{ReleasedEvents: 1}) if err != nil { t.Fatalf("confirm remaining suffix: %v", err) } if tail.CommittedCursor("interleave") != 3 { t.Errorf("cursor want 3 (1+1+1), got %d", tail.CommittedCursor("interleave")) } // Both fragment states "a" and "b" must be cleaned up after the final // confirm. epB.snapStartSeq advanced to 2 by the partial confirm, so // the re-prepared suffix [A2] covers A2 at absolute seq 2; the // cleanup loop's confirmedEndSeq = 2 + 1 = 3 drops every entry with // seq < 3, which removes both A and B. cs := tail.channelState["interleave"] if _, exists := cs.fragmentState["a"]; exists { t.Error("fragmentState[\"a\"] must be removed after A2 confirmed") } if _, exists := cs.fragmentState["b"]; exists { t.Error("fragmentState[\"b\"] must be removed after B1 confirmed") } // No pending entries remain. if len(cs.pendingEntries) != 0 { t.Errorf("pendingEntries want 0, got %d", len(cs.pendingEntries)) } // Re-preparing the first A epoch must fail (consumed). _, err = tail.PrepareRelease(epA.ID()) if err == nil { t.Error("re-prepare consumed epoch must fail") } }) } // terminal and provider-error triggers create an exact safe-prefix ready // epoch that excludes the control event itself, and that the signal is // EvidenceTailSignalTrigger. Outstanding tokens after the trigger are // properly invalidated. func TestEvidenceTailTerminalGateConfiguredTrigger(t *testing.T) { allKinds := []EventKind{EventKindTextDelta, EventKindReasoningDelta} t.Run("configuredTerminalTriggerExcludesControlEvent", func(t *testing.T) { req, err := NewFilterHoldRequirementTerminalGate("ch", allKinds, EventKindTerminal) if err != nil { t.Fatalf("build req: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{mustBind(t, req)}) if err != nil { t.Fatalf("plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("tail: %v", err) } // Add 3 text events (data, not trigger). for i := 0; i < 3; i++ { ev, _ := NewTextDeltaEvent("ch", fmt.Sprintf("data-%d", i), now) tail.Append(ev) } // Append terminal trigger. evTerm, _ := NewTerminalEvent("ch", now) ep, signal, err := tail.Append(evTerm) if err != nil { t.Fatalf("append terminal: %v", err) } if signal != EvidenceTailSignalTrigger { t.Errorf("signal want trigger, got %s", signal) } if ep.ID() == 0 { t.Fatal("terminal trigger must produce an epoch") } if !ep.Triggered() { t.Error("epoch must be marked triggered") } // Prepare release: snapshot must contain only the 3 data events, // NOT the terminal event. pr, err := tail.PrepareRelease(ep.ID()) if err != nil { t.Fatalf("prepare: %v", err) } if len(pr.ReleaseEvents()) != 3 { t.Errorf("snapshot want 3 data events, got %d", len(pr.ReleaseEvents())) } // Full confirm. if err := tail.ConfirmRelease(pr.Token(), ReleaseConfirmation{ReleasedEvents: 3}); err != nil { t.Fatalf("confirm: %v", err) } // Pending must be empty (terminal event excluded from pending). cs := tail.channelState["ch"] if len(cs.pendingEntries) != 0 { t.Errorf("pending must be 0 after terminal release, got %d", len(cs.pendingEntries)) } }) t.Run("configuredProviderErrorTriggerExcludesControlEvent", func(t *testing.T) { req, err := NewFilterHoldRequirementTerminalGate("ch", allKinds, EventKindProviderError) if err != nil { t.Fatalf("build req: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{mustBind(t, req)}) if err != nil { t.Fatalf("plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("tail: %v", err) } // Add 2 data events. for i := 0; i < 2; i++ { ev, _ := NewTextDeltaEvent("ch", fmt.Sprintf("data-%d", i), now) tail.Append(ev) } // Append provider-error trigger. evPE, _ := NewProviderErrorEvent("ch", mustDesc(t, "provider_error", "err", "provider_failure", ""), mustCauses(t, "stage", "err", "consumer", "filter", "rule"), now) ep, signal, err := tail.Append(evPE) if err != nil { t.Fatalf("append provider-error: %v", err) } if signal != EvidenceTailSignalTrigger { t.Errorf("signal want trigger, got %s", signal) } if ep.ID() == 0 { t.Fatal("provider-error trigger must produce an epoch") } pr, err := tail.PrepareRelease(ep.ID()) if err != nil { t.Fatalf("prepare: %v", err) } // Only 2 data events, not the provider-error control event. if len(pr.ReleaseEvents()) != 2 { t.Errorf("snapshot want 2 data events, got %d", len(pr.ReleaseEvents())) } // Verify none of the release events is a provider-error kind. for _, re := range pr.ReleaseEvents() { if re.Kind() == EventKindProviderError { t.Error("control event must not appear in release snapshot") } } }) t.Run("terminalTriggerWithNoPendingData", func(t *testing.T) { req, err := NewFilterHoldRequirementTerminalGate("ch", allKinds, EventKindTerminal) if err != nil { t.Fatalf("build req: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{mustBind(t, req)}) if err != nil { t.Fatalf("plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("tail: %v", err) } // No data events, just terminal trigger. evTerm, _ := NewTerminalEvent("ch", now) ep, signal, err := tail.Append(evTerm) if err != nil { t.Fatalf("append terminal: %v", err) } if signal != EvidenceTailSignalTrigger { t.Errorf("signal want trigger, got %s", signal) } if ep.ID() == 0 { t.Fatal("terminal trigger must produce epoch even with no data") } // Prepare should return empty snapshot. _, err = tail.PrepareRelease(ep.ID()) if err == nil { t.Error("empty snapshot should be rejected on prepare") } }) t.Run("subsequentAppendAfterTerminal", func(t *testing.T) { req, err := NewFilterHoldRequirementTerminalGate("ch", allKinds, EventKindTerminal) if err != nil { t.Fatalf("build req: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{mustBind(t, req)}) if err != nil { t.Fatalf("plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("tail: %v", err) } // Add data, terminal trigger, confirm. for i := 0; i < 2; i++ { ev, _ := NewTextDeltaEvent("ch", "data", now) tail.Append(ev) } evTerm, _ := NewTerminalEvent("ch", now) ep, _, _ := tail.Append(evTerm) pr, _ := tail.PrepareRelease(ep.ID()) tail.ConfirmRelease(pr.Token(), ReleaseConfirmation{ReleasedEvents: 2}) // Now append more data and a new terminal trigger. for i := 0; i < 2; i++ { ev, _ := NewTextDeltaEvent("ch", fmt.Sprintf("more-%d", i), now) tail.Append(ev) } evTerm2, _ := NewTerminalEvent("ch", now) ep2, signal2, err := tail.Append(evTerm2) if err != nil { t.Fatalf("append second terminal: %v", err) } if signal2 != EvidenceTailSignalTrigger { t.Errorf("second signal want trigger, got %s", signal2) } pr2, _ := tail.PrepareRelease(ep2.ID()) if err != nil { t.Fatalf("prepare second: %v", err) } if len(pr2.ReleaseEvents()) != 2 { t.Errorf("second snapshot want 2 data events, got %d", len(pr2.ReleaseEvents())) } }) } // --- test helper functions --- func mustDesc(t *testing.T, errorType, code, message, param string) ExternalDescriptor { t.Helper() d, err := NewExternalDescriptor(errorType, code, message, param) if err != nil { t.Fatalf("external descriptor: %v", err) } return d } func mustCauses(t *testing.T, stage, code, consumer, filter, ruleID string) FailureCauseChain { t.Helper() c, err := NewFailureCause(stage, code, consumer, filter, ruleID) if err != nil { t.Fatalf("failure cause: %v", err) } chain, err := NewFailureCauseChain([]FailureCause{c}) if err != nil { t.Fatalf("failure cause chain: %v", err) } return chain } func mustBind(t *testing.T, req FilterHoldRequirement) FilterHoldBinding { t.Helper() b, err := NewFilterHoldBinding("f", req, true) if err != nil { t.Fatalf("bind: %v", err) } return b } func buildThresholdTail(t *testing.T, kinds []EventKind, threshold int) *EvidenceTail { t.Helper() req, err := NewFilterHoldRequirementRollingWithMaxBuffer("ch", kinds, threshold, 4096) if err != nil { t.Fatalf("build req: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{mustBind(t, req)}) if err != nil { t.Fatalf("plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("tail: %v", err) } // Fill to threshold. for i := 0; i < 3; i++ { ev, _ := NewTextDeltaEvent("ch", repeatRune('a', threshold/3+1), now) tail.Append(ev) } return tail } func findLatestEpoch(t *testing.T, tail *EvidenceTail) uint64 { t.Helper() var ep uint64 for _, rec := range tail.epochs { if rec.epochID > ep { ep = rec.epochID } } if ep == 0 { t.Fatal("no epoch found") } return ep } func repeatRune(r rune, n int) string { b := make([]rune, n) for i := range b { b[i] = r } return string(b) } // TestEvidenceTailPreservesExactKoreanSuffixContent verifies that // 2/200/500-rune oversized Korean events keep exact trailing content through // release/confirm. The table drives each bound with a single Korean event // larger than the bound, a release+confirm cycle, and an ExactLookBehind // assertion on the trailing bound runes. func TestEvidenceTailPreservesExactKoreanSuffixContent(t *testing.T) { textKinds := []EventKind{EventKindTextDelta} type koreanCase struct { name string bound int runes int } cases := []koreanCase{ {"twoRuneBound", 2, 50}, {"twoHundredRuneBound", 200, 500}, {"fiveHundredRuneBound", 500, 1500}, } // distinctHangul produces n position-distinct valid Hangul syllable runes // so that a wrong suffix offset always produces a different rune, not a // same-rune false match. The largest case remains within the 11,172-code // point Hangul syllables block. distinctHangul := func(n int) string { b := make([]rune, n) for i := 0; i < n; i++ { b[i] = rune(0xAC00 + i%11172) } return string(b) } for _, tc := range cases { tc := tc t.Run(tc.name, func(t *testing.T) { req, err := NewFilterHoldRequirementRollingWithMaxBuffer("ch-kr", textKinds, tc.bound, 2*tc.runes) if err != nil { t.Fatalf("build req: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{mustBind(t, req)}) if err != nil { t.Fatalf("plan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("tail: %v", err) } // Single Korean event with tc.runes unique Hangul syllables. koreanText := distinctHangul(tc.runes) ev, err := NewTextDeltaEvent("ch-kr", koreanText, now) if err != nil { t.Fatalf("new Korean text delta: %v", err) } if _, sig, err := tail.Append(ev); err != nil { t.Fatalf("append Korean event: %v", err) } else if sig != EvidenceTailSignalNone && sig != EvidenceTailSignalThreshold { t.Errorf("append Korean signal want none or threshold, got %s", sig) } var ep uint64 for _, r := range tail.epochs { if r.epochID > ep { ep = r.epochID } } pr, err := tail.PrepareRelease(ep) if err != nil { t.Fatalf("prepare release: %v", err) } if len(pr.ReleaseEvents()) != 1 { t.Fatalf("release want 1 event, got %d", len(pr.ReleaseEvents())) } releaseText, err := pr.ReleaseEvents()[0].AsTextDelta() if err != nil { t.Fatalf("as text delta (release): %v", err) } if len([]rune(releaseText)) != tc.runes { t.Errorf("release text want %d runes, got %d", tc.runes, len([]rune(releaseText))) } // Full confirm. if err := tail.ConfirmRelease(pr.Token(), ReleaseConfirmation{ReleasedEvents: 1}); err != nil { t.Fatalf("confirm: %v", err) } // EffectiveLookBehind must be a deep copy of at most tc.bound runes. lb := tail.EffectiveLookBehind("ch-kr") if len(lb) != 1 { t.Fatalf("lookBehind want 1 event, got %d", len(lb)) } lbText, err := lb[0].AsTextDelta() if err != nil { t.Fatalf("as text delta (lookBehind): %v", err) } if len([]rune(lbText)) != tc.bound { t.Errorf("lookBehind trailing want %d runes, got %d", tc.bound, len([]rune(lbText))) } // Exact trailing content: the last tc.bound runes must be the // suffix of koreanText. With distinct-rune fixture, a wrong // whole-rune offset in the trim logic flips to a different rune. allR := []rune(koreanText) wantSuffix := string(allR[len(allR)-tc.bound:]) if lbText != wantSuffix { t.Errorf("lookBehind text want exact suffix of %d chars, got %q", tc.bound, lbText) } // Exact release text match: release must equal the full koreanText. if releaseText != koreanText { t.Errorf("release text want exact %q, got %q", koreanText, releaseText) } // Mutation-isolated requery: mutate the first returned event // in-place and verify that re-querying EffectiveLookBehind still // returns the untouched exact suffix, proving the accessor // returns a deep copy. mutatedOriginal := lbText lb[0] = NormalizedEvent{} lbRequery := tail.EffectiveLookBehind("ch-kr") if len(lbRequery) != 1 { t.Fatalf("requery lookBehind want 1 event, got %d", len(lbRequery)) } lbRequeryText, err := lbRequery[0].AsTextDelta() if err != nil { t.Fatalf("as text delta (requery): %v", err) } if lbRequeryText != mutatedOriginal { t.Errorf("defensive copy requery want exact %q after mutation, got %q", mutatedOriginal, lbRequeryText) } // Cursor must be 1. if tail.CommittedCursor("ch-kr") != 1 { t.Errorf("cursor want 1, got %d", tail.CommittedCursor("ch-kr")) } }) } }