package streamgate import ( "context" "errors" "fmt" "sync" "sync/atomic" "testing" "time" ) // testNow is a deterministic timestamp used in stream releaser tests. var releaserNow = time.Date(2026, 7, 24, 12, 0, 0, 0, time.UTC) // testCanceller records cancel attempts and can be programmed to fail. type testCanceller struct { cancelCount int cancelErr error } func (c *testCanceller) CancelAttempt(ctx context.Context, attemptID string) error { c.cancelCount++ return c.cancelErr } // setupReleaser creates a StreamReleaser with a rolling_window EvidenceTail, // CommitBoundary, and canceller. The tail is configured so that appending // enough events triggers an epoch via the rolling threshold. // // evidenceRunes controls the rolling threshold; maxBuffer is the hard limit. func setupReleaser(t *testing.T, channel string, evidenceRunes, maxBuffer int) (*StreamReleaser, *testSink, *testCanceller) { t.Helper() // Create a rolling_window plan with a low threshold so a few events // trigger an epoch quickly. kinds := []EventKind{EventKindTextDelta, EventKindReasoningDelta} var req FilterHoldRequirement if maxBuffer > 0 { var err error req, err = NewFilterHoldRequirementRollingWithMaxBuffer(channel, kinds, evidenceRunes, maxBuffer) if err != nil { t.Fatalf("NewFilterHoldRequirementRollingWithMaxBuffer: %v", err) } } else { var err error req, err = NewFilterHoldRequirementRolling(channel, kinds, evidenceRunes) if err != nil { t.Fatalf("NewFilterHoldRequirementRolling: %v", err) } } binding, err := NewFilterHoldBinding("f1", req, true) if err != nil { t.Fatalf("NewFilterHoldBinding: %v", err) } plan, err := NewEvidencePlan([]FilterHoldBinding{binding}) if err != nil { t.Fatalf("NewEvidencePlan: %v", err) } tail, err := NewEvidenceTail(plan) if err != nil { t.Fatalf("NewEvidenceTail: %v", err) } sink := &testSink{} boundary, err := NewCommitBoundary(sink) if err != nil { t.Fatalf("NewCommitBoundary: %v", err) } canceller := &testCanceller{} releaser, err := NewStreamReleaser(tail, boundary, canceller) if err != nil { t.Fatalf("NewStreamReleaser: %v", err) } return releaser, sink, canceller } // appendDataEvents creates and appends text_delta events to the tail until // an epoch is triggered (rolling threshold reached). Returns the epoch. func appendDataEvents(t *testing.T, tail *EvidenceTail, channel string, count int) EvidenceEpoch { t.Helper() var epoch EvidenceEpoch for i := 0; i < count; i++ { ne, err := NewTextDeltaEvent(channel, "chunk", releaserNow) if err != nil { t.Fatalf("NewTextDeltaEvent: %v", err) } e, signal, err := tail.Append(ne) if err != nil { t.Fatalf("Append: %v", err) } if signal == EvidenceTailSignalThreshold { epoch = e } } return epoch } // createTextReleaseEvent creates a ReleaseEvent of kind text_delta. func createTextReleaseEvent(channel, text string) ReleaseEvent { ev, err := NewReleaseTextDeltaEvent(channel, text, releaserNow) if err != nil { panic(err) } return ev } // createSuccessTerminalResult creates a TerminalResult of kind success. func createSuccessTerminalResult(channel string) TerminalResult { tr, err := NewSuccessTerminalResult(channel, releaserNow) if err != nil { panic(err) } return tr } // createErrorTerminalResult creates an error TerminalResult. func createErrorTerminalResult(channel string) TerminalResult { desc, err := NewExternalDescriptor("generic", "internal_error", "an_internal_error", "") if err != nil { panic(fmt.Sprintf("createErrorTerminalResult NewExternalDescriptor failed: %v", err)) } causes, err := NewFailureCauseChain([]FailureCause{}) if err != nil { panic(fmt.Sprintf("createErrorTerminalResult NewFailureCauseChain failed: %v", err)) } tr, err := NewErrorTerminalResult(channel, desc, causes, releaserNow) if err != nil { panic(fmt.Sprintf("createErrorTerminalResult NewErrorTerminalResult failed: %v", err)) } return tr } // --------------------------------------------------------------------------- // REVIEW_API-2: Evidence prepare/confirm with sink progress // --------------------------------------------------------------------------- // TestStreamReleaserConfirmsExactlySinkProgress verifies zero/partial/full // sink progress where confirmed cursor matches sink success count. func TestStreamReleaserConfirmsExactlySinkProgress(t *testing.T) { ctx := context.Background() t.Run("zero_sink_progress", func(t *testing.T) { releaser, sink, _ := setupReleaser(t, "ch-zero", 2, 100) // Begin attempt and stage response start on boundary. releaser.boundary.BeginAttempt("a1") rs, _ := NewResponseStart("ch-zero", 200, nil, releaserNow) releaser.boundary.StageResponseStart("a1", rs) // Append data events to trigger epoch. epoch := appendDataEvents(t, releaser.tail, "ch-zero", 3) // All releases fail. sink.failAll = true prog, err := releaser.ReleaseEpoch(ctx, "a1", epoch.ID()) if err == nil { t.Error("expected error from ReleaseEpoch") } if prog.ReleasedEvents() != 0 { t.Errorf("ReleasedEvents = %d, want 0 (all events failed)", prog.ReleasedEvents()) } }) t.Run("partial_sink_progress", func(t *testing.T) { releaser, sink, _ := setupReleaser(t, "ch-partial", 2, 100) releaser.boundary.BeginAttempt("a1") rs, _ := NewResponseStart("ch-partial", 200, nil, releaserNow) releaser.boundary.StageResponseStart("a1", rs) epoch := appendDataEvents(t, releaser.tail, "ch-partial", 5) // 2 successes, then fail. sink.failAfterSuccesses = 2 prog, err := releaser.ReleaseEpoch(ctx, "a1", epoch.ID()) if err == nil { t.Error("expected partial error") } if prog.ReleasedEvents() != 2 { t.Errorf("ReleasedEvents = %d, want 2", prog.ReleasedEvents()) } }) t.Run("partial_sink_progress_two_succeed_then_fail", func(t *testing.T) { releaser, sink, _ := setupReleaser(t, "ch-partial2", 2, 100) releaser.boundary.BeginAttempt("a1") rs, _ := NewResponseStart("ch-partial2", 200, nil, releaserNow) releaser.boundary.StageResponseStart("a1", rs) epoch := appendDataEvents(t, releaser.tail, "ch-partial2", 5) // 2 successes, then fail. sink.failAfterSuccesses = 2 prog, err := releaser.ReleaseEpoch(ctx, "a1", epoch.ID()) if err == nil { t.Error("expected partial error") } if prog.ReleasedEvents() != 2 { t.Errorf("ReleasedEvents = %d, want 2", prog.ReleasedEvents()) } }) t.Run("full_sink_progress", func(t *testing.T) { releaser, sink, _ := setupReleaser(t, "ch-full", 2, 100) releaser.boundary.BeginAttempt("a1") rs, _ := NewResponseStart("ch-full", 200, nil, releaserNow) releaser.boundary.StageResponseStart("a1", rs) epoch := appendDataEvents(t, releaser.tail, "ch-full", 3) prog, err := releaser.ReleaseEpoch(ctx, "a1", epoch.ID()) if err != nil { t.Fatalf("ReleaseEpoch: %v", err) } if prog.ReleasedEvents() != 3 { t.Errorf("ReleasedEvents = %d, want 3", prog.ReleasedEvents()) } // Cursor should advance by 3. if got := releaser.tail.CommittedCursor("ch-full"); got != 3 { t.Errorf("committed cursor = %d, want 3", got) } // Sink should have received 3 releases + 1 start. if len(sink.releases) != 3 { t.Errorf("sink releases = %d, want 3", len(sink.releases)) } if len(sink.responseStarts) != 1 { t.Errorf("sink responseStarts = %d, want 1", len(sink.responseStarts)) } }) } // TestStreamReleaserTerminalBeforeEvidenceExposesOneOutcome verifies that the // pass path exposes start/output+success terminal and error path exposes // only error terminal without releasing pending/start. func TestStreamReleaserTerminalBeforeEvidenceExposesOneOutcome(t *testing.T) { ctx := context.Background() t.Run("pass_exposes_start_and_terminal", func(t *testing.T) { releaser, sink, _ := setupReleaser(t, "ch-pass", 2, 100) releaser.boundary.BeginAttempt("a1") rs, _ := NewResponseStart("ch-pass", 200, nil, releaserNow) releaser.boundary.StageResponseStart("a1", rs) // Create and release an epoch. epoch := appendDataEvents(t, releaser.tail, "ch-pass", 2) prog, err := releaser.ReleaseEpoch(ctx, "a1", epoch.ID()) if err != nil { t.Fatalf("ReleaseEpoch: %v", err) } if prog.ReleasedEvents() != 2 { t.Errorf("ReleasedEvents = %d, want 2", prog.ReleasedEvents()) } // Terminal success. tr := createSuccessTerminalResult("ch-pass") prog2, err := releaser.ReleaseTerminalEpoch(ctx, "a1", epoch.ID(), tr) if err != nil { t.Fatalf("ReleaseTerminalEpoch: %v", err) } _ = prog2 if len(sink.responseStarts) != 1 { t.Errorf("responseStarts = %d, want 1", len(sink.responseStarts)) } if len(sink.terminals) != 1 { t.Errorf("terminals = %d, want 1", len(sink.terminals)) } if len(sink.releases) != 2 { t.Errorf("releases = %d, want 2", len(sink.releases)) } }) t.Run("error_exposes_only_error_terminal", func(t *testing.T) { releaser, sink, canceller := setupReleaser(t, "ch-err", 2, 100) releaser.boundary.BeginAttempt("a2") rs, _ := NewResponseStart("ch-err", 200, nil, releaserNow) releaser.boundary.StageResponseStart("a2", rs) // Create some pending events but don't release them. appendDataEvents(t, releaser.tail, "ch-err", 2) // Error terminal without releasing events. tr := createErrorTerminalResult("ch-err") prog, err := releaser.ReleaseTerminalEpoch(ctx, "a2", 999, tr) if err != nil { t.Fatalf("ReleaseTerminalEpoch: %v", err) } _ = prog // Should have called cancel. if canceller.cancelCount != 1 { t.Errorf("cancelCount = %d, want 1", canceller.cancelCount) } if len(sink.responseStarts) != 0 { t.Errorf("responseStarts = %d, want 0 (error path)", len(sink.responseStarts)) } if len(sink.terminals) != 1 { t.Errorf("terminals = %d, want 1", len(sink.terminals)) } if len(sink.releases) != 0 { t.Errorf("releases = %d, want 0 (no pending release)", len(sink.releases)) } }) } // TestStreamReleaserDoesNotDuplicatePreparedRelease verifies that repeated // epoch calls do not duplicate downstream events. func TestStreamReleaserDoesNotDuplicatePreparedRelease(t *testing.T) { ctx := context.Background() releaser, sink, _ := setupReleaser(t, "ch-dup", 2, 100) releaser.boundary.BeginAttempt("a1") rs, _ := NewResponseStart("ch-dup", 200, nil, releaserNow) releaser.boundary.StageResponseStart("a1", rs) // First epoch. epoch1 := appendDataEvents(t, releaser.tail, "ch-dup", 2) prog, err := releaser.ReleaseEpoch(ctx, "a1", epoch1.ID()) if err != nil { t.Fatalf("ReleaseEpoch 1: %v", err) } if prog.ReleasedEvents() != 2 { t.Errorf("first ReleasedEvents = %d, want 2", prog.ReleasedEvents()) } // Second epoch (more events to trigger another). epoch2 := appendDataEvents(t, releaser.tail, "ch-dup", 2) prog2, err := releaser.ReleaseEpoch(ctx, "a1", epoch2.ID()) if err != nil { t.Fatalf("ReleaseEpoch 2: %v", err) } if prog2.ReleasedEvents() != 2 { t.Errorf("second ReleasedEvents = %d, want 2", prog2.ReleasedEvents()) } // Total should be 4 releases (2+2), no duplicates. if len(sink.releases) != 4 { t.Errorf("total releases = %d, want 4 (2+2)", len(sink.releases)) } } // --------------------------------------------------------------------------- // REVIEW_API-3: Attempt replacement, idle and overflow // --------------------------------------------------------------------------- // TestStreamReleaserReplacementResetsTailGeneration verifies that boundary // attempt and tail pending/prepared token are replaced together. func TestStreamReleaserReplacementResetsTailGeneration(t *testing.T) { ctx := context.Background() releaser, _, _ := setupReleaser(t, "ch-rep", 2, 100) // Begin attempt and stage start. releaser.boundary.BeginAttempt("old") rs, _ := NewResponseStart("ch-rep", 200, nil, releaserNow) releaser.boundary.StageResponseStart("old", rs) // Append events. appendDataEvents(t, releaser.tail, "ch-rep", 2) // Replace. err := releaser.ReplaceUncommittedAttempt(ctx, "old", "new") if err != nil { t.Fatalf("ReplaceUncommittedAttempt: %v", err) } // Tail cursor should be 0 after replace. if got := releaser.tail.CommittedCursor("ch-rep"); got != 0 { t.Errorf("tail cursor after replace = %d, want 0", got) } // Boundary attempt should be cleared. if got := releaser.boundary.CurrentAttempt(); got != "" { t.Errorf("boundary attempt after replace = %q, want empty", got) } } // TestStreamReleaserIdleReleasesNoPending verifies that idle error results // in zero release/start and correct cancel→terminal ordering. func TestStreamReleaserIdleReleasesNoPending(t *testing.T) { ctx := context.Background() releaser, sink, canceller := setupReleaser(t, "ch-idle", 2, 100) releaser.boundary.BeginAttempt("a1") rs, _ := NewResponseStart("ch-idle", 200, nil, releaserNow) releaser.boundary.StageResponseStart("a1", rs) // Append some events to create pending state. appendDataEvents(t, releaser.tail, "ch-idle", 2) // FailPending. tr := createErrorTerminalResult("ch-idle") prog, err := releaser.FailPending(ctx, "a1", tr) if err != nil { t.Fatalf("FailPending: %v", err) } if prog.ReleasedEvents() != 0 { t.Errorf("ReleasedEvents = %d, want 0", prog.ReleasedEvents()) } // No responses or releases. if len(sink.responseStarts) != 0 { t.Errorf("responseStarts = %d, want 0", len(sink.responseStarts)) } if len(sink.releases) != 0 { t.Errorf("releases = %d, want 0", len(sink.releases)) } // Cancel should have been called. if canceller.cancelCount != 1 { t.Errorf("cancelCount = %d, want 1", canceller.cancelCount) } if len(sink.terminals) != 1 { t.Errorf("terminals = %d, want 1", len(sink.terminals)) } } // TestStreamReleaserOverflowCancelsBeforeSingleTerminal verifies that S19 // overflow results in no partial release, and terminal exactly once. func TestStreamReleaserOverflowCancelsBeforeSingleTerminal(t *testing.T) { ctx := context.Background() // Use very small buffer to trigger overflow. // rolling_window with evidence 1 and maxBuffer 10 (minimum). releaser, sink, canceller := setupReleaser(t, "ch-overflow", 1, 10) // Append events, expecting overflow signal. overflowed := false for i := 0; i < 50; i++ { ne, err := NewTextDeltaEvent("ch-overflow", "chunk", releaserNow) if err != nil { t.Fatalf("NewTextDeltaEvent: %v", err) } _, signal, err := releaser.tail.Append(ne) if err != nil { t.Fatalf("Append: %v", err) } if signal == EvidenceTailSignalBufferOverflow { overflowed = true break } } if !overflowed { t.Skip("expected overflow signal not generated with current configuration") } // FailPending for overflow recovery. tr := createErrorTerminalResult("ch-overflow") prog, err := releaser.FailPending(ctx, "a1", tr) if err != nil { t.Fatalf("FailPending: %v", err) } if prog.ReleasedEvents() != 0 { t.Errorf("ReleasedEvents = %d, want 0 (overflow)", prog.ReleasedEvents()) } if len(sink.terminals) != 1 { t.Errorf("terminals = %d, want 1 (exactly once)", len(sink.terminals)) } _ = canceller } // TestStreamReleaserCancelFailureAppendsBoundedCause verifies that when // cancel fails, the cause is appended to the terminal result. func TestStreamReleaserCancelFailureAppendsBoundedCause(t *testing.T) { ctx := context.Background() releaser, sink, canceller := setupReleaser(t, "ch-cancel-fail", 2, 100) releaser.boundary.BeginAttempt("a1") rs, _ := NewResponseStart("ch-cancel-fail", 200, nil, releaserNow) releaser.boundary.StageResponseStart("a1", rs) // Program canceller to fail. canceller.cancelErr = errors.New("test: cancel failed") appendDataEvents(t, releaser.tail, "ch-cancel-fail", 2) tr := createErrorTerminalResult("ch-cancel-fail") prog, err := releaser.FailPending(ctx, "a1", tr) if err != nil { t.Fatalf("FailPending: %v", err) } _ = prog // Terminal should still be committed exactly once. if len(sink.terminals) != 1 { t.Errorf("terminals = %d, want 1", len(sink.terminals)) } // The terminal result should include the cancel failure cause. lastTerminal := sink.terminals[len(sink.terminals)-1] causes := lastTerminal.FailureCauses() if causes.Len() == 0 { t.Error("expected failure causes to include cancel failure") } else { found := false for i := 0; i < causes.Len(); i++ { c, _ := causes.At(i) if c.Code() == "attempt_cancel_failed" { found = true break } } if !found { t.Error("expected 'attempt_cancel_failed' cause code") } } } // TestStreamReleaserConcurrentEpochsAreSerialized verifies that concurrent // release calls through the releaser serializer do not cause data races // (the tail itself is not thread-safe, so appends must be serialized). func TestStreamReleaserConcurrentEpochsAreSerialized(t *testing.T) { ctx := context.Background() releaser, _, _ := setupReleaser(t, "ch-conc", 2, 100) releaser.boundary.BeginAttempt("a1") rs, _ := NewResponseStart("ch-conc", 200, nil, releaserNow) releaser.boundary.StageResponseStart("a1", rs) // Pre-create all epochs sequentially (tail is not thread-safe). epochs := make([]EvidenceEpoch, 5) for i := 0; i < 5; i++ { epochs[i] = appendDataEvents(t, releaser.tail, "ch-conc", 2) } var wg sync.WaitGroup var successful int64 var errors int64 for i := 0; i < 5; i++ { wg.Add(1) go func(idx int) { defer wg.Done() _, err := releaser.ReleaseEpoch(ctx, "a1", epochs[idx].ID()) if err == nil { atomic.AddInt64(&successful, 1) } else { atomic.AddInt64(&errors, 1) } }(i) } wg.Wait() // All events should have been processed without race. total := atomic.LoadInt64(&successful) + atomic.LoadInt64(&errors) if total != 5 { t.Errorf("total results = %d, want 5", total) } } // TestStreamReleaserTerminalAfterFullReleaseSkipsSecondaryRelease verifies // that after an epoch is fully released and confirmed, ReleaseTerminalEpoch // does not attempt another release. func TestStreamReleaserTerminalAfterFullReleaseSkipsSecondaryRelease(t *testing.T) { ctx := context.Background() releaser, sink, _ := setupReleaser(t, "ch-skip", 2, 100) releaser.boundary.BeginAttempt("a1") rs, _ := NewResponseStart("ch-skip", 200, nil, releaserNow) releaser.boundary.StageResponseStart("a1", rs) // Release epoch fully. epoch := appendDataEvents(t, releaser.tail, "ch-skip", 2) prog, err := releaser.ReleaseEpoch(ctx, "a1", epoch.ID()) if err != nil { t.Fatalf("ReleaseEpoch: %v", err) } if prog.ReleasedEvents() != 2 { t.Errorf("ReleasedEvents = %d, want 2", prog.ReleasedEvents()) } // Terminal after full release. tr := createSuccessTerminalResult("ch-skip") prog2, err := releaser.ReleaseTerminalEpoch(ctx, "a1", epoch.ID(), tr) if err != nil { t.Fatalf("ReleaseTerminalEpoch: %v", err) } _ = prog2 // Should have 2 releases + 1 start, no extra. if len(sink.releases) != 2 { t.Errorf("releases = %d, want 2 (no secondary release)", len(sink.releases)) } if len(sink.responseStarts) != 1 { t.Errorf("responseStarts = %d, want 1", len(sink.responseStarts)) } if len(sink.terminals) != 1 { t.Errorf("terminals = %d, want 1", len(sink.terminals)) } }