返回 DeepSeek-Reasonix
transcript_test.go
根目录 / internal / session / transcript_test.go
1 package session
2
3 import (
4 "testing"
5
6 "reasonix/internal/event"
7 "reasonix/internal/eventwire"
8 "reasonix/internal/transcript"
9 "reasonix/internal/turnevent"
10 )
11
12 func TestRuntimeTranscriptCoverageIncludesNonVisibleBusinessCommits(t *testing.T) {
13 _, runtime := reviewRuntime(t)
14 p := runtime.Transcript()
15 before, err := p.Snapshot(transcript.PageRequest{})
16 if err != nil {
17 t.Fatal(err)
18 }
19 if _, err := runtime.Session().AppendBatch(t.Context(), "configuration", []Event{{Kind: "session/config", Payload: []byte(`{"modelRef":"test"}`)}}); err != nil {
20 t.Fatal(err)
21 }
22 after, err := p.Snapshot(transcript.PageRequest{})
23 if err != nil {
24 t.Fatal(err)
25 }
26 if after.CoveredThroughSeq != runtime.StateSnapshot().Session.EventSequence || after.CoveredThroughSeq <= before.CoveredThroughSeq {
27 t.Fatalf("non-visible commit lost business coverage: before=%d after=%d business=%d", before.CoveredThroughSeq, after.CoveredThroughSeq, runtime.StateSnapshot().Session.EventSequence)
28 }
29 if len(after.Records) != len(before.Records) {
30 t.Fatal("configuration commit manufactured a chat record")
31 }
32 for index, text := range []string{"first ", "second"} {
33 e := eventwire.ToWire(event.Event{Kind: event.Text, MessageID: "answer", AttemptID: "answer", Text: text})
34 // Deliberately unrelated to the business sequence. A frame number is
35 // never the accepted-log cursor, even when the values happen to match.
36 if err := runtime.PublishTranscriptFrame(turnevent.Envelope{SessionID: runtime.Ref().SessionID, RuntimeEpoch: runtime.StateSnapshot().Epoch, TurnID: "turn", Sequence: uint64(1000 + index), Kind: e.Kind, Status: event.TurnInProgress, Event: e}); err != nil {
37 t.Fatal(err)
38 }
39 cut, err := p.Snapshot(transcript.PageRequest{})
40 if err != nil {
41 t.Fatal(err)
42 }
43 if cut.CoveredThroughSeq != after.CoveredThroughSeq || cut.ProjectionRevision <= after.ProjectionRevision {
44 t.Fatalf("frame conflates revision and coverage: prior=%+v current=%+v", after.Boundary, cut.Boundary)
45 }
46 after = cut
47 }
48 if len(after.Records) != 1 || after.Records[0].Message.Content != "first second" {
49 t.Fatalf("streaming prefix was not retained: %+v", after.Records)
50 }
51 }
52
53 func TestRuntimeTranscriptSurvivesExecutionReplacement(t *testing.T) {
54 _, runtime := reviewRuntime(t)
55 p := runtime.Transcript()
56 if p == nil {
57 t.Fatal("runtime lacks its transcript publisher")
58 }
59 first := &testExecution{phase: RuntimeIdle, runtime: runtime}
60 first.gen = runtime.BindExecution(first)
61 if first.gen == 0 {
62 t.Fatal("initial execution was not bound")
63 }
64 second := &testExecution{phase: RuntimeIdle, runtime: runtime}
65 second.gen = runtime.ReplaceExecution(first.gen, second)
66 if second.gen == 0 {
67 t.Fatal("idle execution replacement was rejected")
68 }
69 t.Cleanup(func() { runtime.UnbindExecution(second.gen) })
70 if runtime.Transcript() != p {
71 t.Fatal("execution replacement created another transcript authority")
72 }
73 if _, err := runtime.Session().AppendBatch(t.Context(), "after-replacement", []Event{{Kind: "session/config", Payload: []byte(`{"modelRef":"replacement"}`)}}); err != nil {
74 t.Fatal(err)
75 }
76 cut, err := p.Snapshot(transcript.PageRequest{})
77 if err != nil {
78 t.Fatal(err)
79 }
80 if cut.CoveredThroughSeq != runtime.StateSnapshot().Session.EventSequence || cut.CoveredThroughSeq == 0 {
81 t.Fatal("original publisher stopped observing commits after replacement")
82 }
83 }
84
85 func TestRuntimeTranscriptRetractionResetsReadersAndPreservesActiveOutput(t *testing.T) {
86 _, runtime := reviewRuntime(t)
87 ctx := t.Context()
88 if _, err := runtime.Session().AppendBatch(ctx, "input", []Event{{Kind: "message/complete", Payload: []byte(`{"message":{"id":"withdrawn","role":"user","content":"synthetic input"}}`)}}); err != nil {
89 t.Fatal(err)
90 }
91 e := eventwire.ToWire(event.Event{Kind: event.Text, MessageID: "active-answer", AttemptID: "active-answer", Text: "retained output"})
92 if err := runtime.PublishTranscriptFrame(turnevent.Envelope{SessionID: runtime.Ref().SessionID, RuntimeEpoch: runtime.StateSnapshot().Epoch, Kind: e.Kind, Event: e}); err != nil {
93 t.Fatal(err)
94 }
95 before, err := runtime.FollowTranscript(ctx, transcript.FollowRequest{})
96 if err != nil {
97 t.Fatal(err)
98 }
99 if _, err := runtime.Session().AppendBatch(ctx, "withdraw", []Event{{Kind: "message/retract", Payload: []byte(`{"messageIds":["withdrawn"]}`)}}); err != nil {
100 t.Fatal(err)
101 }
102 after, err := runtime.Transcript().Snapshot(transcript.PageRequest{})
103 if err != nil {
104 t.Fatal(err)
105 }
106 if after.Identity.RewriteEpoch <= before.Snapshot.Identity.RewriteEpoch || after.CoveredThroughSeq != runtime.StateSnapshot().Session.EventSequence {
107 t.Fatal("retraction did not invalidate the old reading cut at its committed sequence")
108 }
109 if len(after.Records) != 1 || after.Records[0].Message.Content != "retained output" {
110 t.Fatalf("retraction lost unrelated active output: %+v", after.Records)
111 }
112 }
113
113 lines GO