iop/packages/go/streamgate/evidence_tail_test.go
toki 8a4f6c55a1 sync: roadmap, skills, test inventory, streamgate package, docs updates
- Update roadmap milestones and phase docs across multiple phases
- Update plan, code-review, create-roadmap, update-roadmap, finalize-task-routing skills
- Update dev-corp-runtime-deploy, dev-runtime-deploy, orchestrate-agent-task-loop skills
- Refactor agent-task-loop dispatch script
- Add streamgate Go package (commit_boundary, evidence_tail, filter_registry, stream_release)
- Add test inventory files (dev, dev-corp, unified)
- Update test smoke tests and rules for dev/dev-corp
- Update docs/edge-local-dev-guide and e2e scripts
- Update inventory-query Go package
- Remove deprecated templates and inventory.yaml files
- Add orchestrate-agent-task-loop tests
2026-07-25 11:41:08 +09:00

4368 lines
133 KiB
Go

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"))
}
})
}
}