Files
DeepSeek-Reasonix/internal/session/transcript_initialization_error_test.go
T
SivanCola 2797455a1d Fix transcript initialization crash during migration
Problem: malformed legacy transcript baselines could crash the Desktop service while startup migration restored a nil projection.

Root cause: session runtime construction discarded NewProjection errors and continued into RestoreRuntime without a valid projection.

Fix: propagate typed initialization failures, release session ownership on every failed path, keep healthy migrations running, add content-free local diagnostics, and queue a deduplicated telemetry-gated exception for the crash dashboard.

Verification: session and transcript package suites, focused Desktop crash/migration tests, targeted race tests, crash worker tests, and worker typecheck pass.
2026-09-18 16:01:36 +08:00

98 lines
3.2 KiB
Go

package session
import (
"context"
"crypto/sha256"
"encoding/json"
"errors"
"fmt"
"log/slog"
"os"
"path/filepath"
"strings"
"testing"
"reasonix/internal/provider"
"reasonix/internal/transcript"
)
type unlockedRuntimeLogHandler struct {
slog.Handler
session *Session
t *testing.T
}
func (h unlockedRuntimeLogHandler) Handle(ctx context.Context, record slog.Record) error {
if !h.session.mu.TryLock() {
h.t.Error("runtime diagnostic logged while holding the session lock")
} else {
h.session.mu.Unlock()
}
return h.Handler.Handle(ctx, record)
}
func TestRuntimeFailureDiagnosticIsWrittenToLocalLog(t *testing.T) {
const secret = "PRIVATE-USER-DATA"
session := &Session{id: secret + "-session", next: 43}
for index := range 100 {
call := fmt.Sprintf("%s-%d", secret, index)
if index >= 98 {
call = secret + "-duplicate"
}
session.recentMessages = append(session.recentMessages, provider.Message{
ID: fmt.Sprintf("%s-message-%d", secret, index), Role: provider.RoleTool,
ToolCallID: call, Content: secret, ReasoningContent: secret,
})
}
path := filepath.Join(t.TempDir(), "service.log")
file, err := os.Create(path)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = file.Close() })
previous := slog.Default()
slog.SetDefault(slog.New(unlockedRuntimeLogHandler{Handler: slog.NewJSONHandler(file, nil), session: session, t: t}))
t.Cleanup(func() { slog.SetDefault(previous) })
ref := SessionRef{HostID: "local", SessionID: session.id}
for range 2 {
runtime, err := newRuntime(ref, session)
var initialization *TranscriptInitializationError
var baseline *transcript.BaselineError
if runtime != nil || !errors.As(err, &initialization) || !errors.As(err, &baseline) {
t.Fatalf("lost typed cause: runtime = %v, error = %v", runtime, err)
}
if initialization.Classification() != "duplicate_record_identity" {
t.Fatalf("online classification = %q", initialization.Classification())
}
}
body, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
if strings.Contains(string(body), secret) || len(body) > 4000 {
t.Fatal("local log contains private data or unbounded diagnostics")
}
lines := strings.Split(strings.TrimSpace(string(body)), "\n")
if len(lines) != 2 {
t.Fatalf("expected one diagnostic per failed attempt, got %d", len(lines))
}
for _, line := range lines {
var entry struct {
Message string `json:"msg"`
Diagnostic map[string]any `json:"diagnostic"`
}
if err := json.Unmarshal([]byte(line), &entry); err != nil {
t.Fatal(err)
}
diagnostic := entry.Diagnostic
if entry.Message != "session transcript initialization failed" || diagnostic["session_key"] != fmt.Sprintf("%x", sha256.Sum256([]byte(session.id))) ||
diagnostic["covered_sequence"] != float64(42) || diagnostic["baseline_message_count"] != float64(96) || diagnostic["baseline_total_message_count"] != float64(100) {
t.Fatalf("incomplete local diagnostic: %+v", entry)
}
baseline, ok := diagnostic["baseline"].(map[string]any)
if !ok || baseline["code"] != "duplicate_record_identity" || baseline["record_index"] != float64(95) || baseline["previous_record_index"] != float64(94) {
t.Fatalf("lost bounded-tail conflict details: %+v", baseline)
}
}
}