Skip to content

Commit 711c0d1

Browse files
author
PAAI
committed
test(gateway): add production-derived compaction-chain evidence
1 parent 4e26f54 commit 711c0d1

2 files changed

Lines changed: 243 additions & 0 deletions

File tree

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
import path from "node:path";
2+
3+
type RawEntry = Record<string, unknown>;
4+
5+
type TranscriptFixtureFile = {
6+
filePath: string;
7+
entries: RawEntry[];
8+
};
9+
10+
type CompactionCheckpointFixture = {
11+
sessionId: string;
12+
preCompaction: {
13+
sessionFile: string;
14+
};
15+
};
16+
17+
export type Repro68765CompactionChainFixture = {
18+
sessionId: string;
19+
store: Record<string, unknown>;
20+
liveSessionFile: string;
21+
transcripts: TranscriptFixtureFile[];
22+
expectedTexts: string[];
23+
expectedMessageIds: string[];
24+
expectedLiveSegmentTexts: string[];
25+
expectedLiveSegmentMessageIds: string[];
26+
};
27+
28+
// Production-derived fixture for PR #68765.
29+
// Source shape: main-session checkpoint chain from a real compaction lineage.
30+
// Redaction: user text, assistant text, timestamps, cwd, provider/model ids,
31+
// and opaque ids are pseudonymized. The file/chain topology is preserved:
32+
// two predecessor `preCompaction.sessionFile` segments plus the live segment.
33+
export function createRepro68765CompactionChainFromProduction(
34+
rootDir: string,
35+
): Repro68765CompactionChainFixture {
36+
const sessionId = "11111111-1111-4111-8111-111111111111";
37+
const checkpoint1SessionId = "22222222-2222-4222-8222-222222222222";
38+
const checkpoint2SessionId = "33333333-3333-4333-8333-333333333333";
39+
const checkpoint1File = path.join(rootDir, `${sessionId}.checkpoint.01.redacted.jsonl`);
40+
const checkpoint2File = path.join(rootDir, `${sessionId}.checkpoint.02.redacted.jsonl`);
41+
const liveSessionFile = path.join(rootDir, `${sessionId}.jsonl`);
42+
43+
const checkpointChain: CompactionCheckpointFixture[] = [
44+
{
45+
sessionId: checkpoint1SessionId,
46+
preCompaction: {
47+
sessionFile: checkpoint1File,
48+
},
49+
},
50+
{
51+
sessionId: checkpoint2SessionId,
52+
preCompaction: {
53+
sessionFile: checkpoint2File,
54+
},
55+
},
56+
];
57+
58+
const transcripts: TranscriptFixtureFile[] = [
59+
{
60+
filePath: checkpoint1File,
61+
entries: [
62+
{
63+
type: "session",
64+
version: 3,
65+
id: "session-root-1",
66+
timestamp: "2026-04-18T02:00:00.000Z",
67+
cwd: "REDACTED_WORKDIR",
68+
},
69+
{
70+
type: "message",
71+
id: "msg-segment-1-user",
72+
parentId: "session-root-1",
73+
timestamp: "2026-04-18T02:00:30.000Z",
74+
message: {
75+
role: "user",
76+
content: [{ type: "text", text: "[redacted chain segment 1 user]" }],
77+
},
78+
},
79+
{
80+
type: "message",
81+
id: "msg-segment-1-assistant",
82+
parentId: "msg-segment-1-user",
83+
timestamp: "2026-04-18T02:00:45.000Z",
84+
message: {
85+
role: "assistant",
86+
content: [{ type: "text", text: "[redacted chain segment 1 assistant]" }],
87+
},
88+
},
89+
],
90+
},
91+
{
92+
filePath: checkpoint2File,
93+
entries: [
94+
{
95+
type: "session",
96+
version: 3,
97+
id: "session-root-2",
98+
timestamp: "2026-04-18T03:10:00.000Z",
99+
cwd: "REDACTED_WORKDIR",
100+
},
101+
{
102+
type: "message",
103+
id: "msg-segment-2-user",
104+
parentId: "session-root-2",
105+
timestamp: "2026-04-18T03:10:30.000Z",
106+
message: {
107+
role: "user",
108+
content: [{ type: "text", text: "[redacted chain segment 2 user]" }],
109+
},
110+
},
111+
{
112+
type: "message",
113+
id: "msg-segment-2-assistant",
114+
parentId: "msg-segment-2-user",
115+
timestamp: "2026-04-18T03:10:45.000Z",
116+
message: {
117+
role: "assistant",
118+
content: [{ type: "text", text: "[redacted chain segment 2 assistant]" }],
119+
},
120+
},
121+
],
122+
},
123+
{
124+
filePath: liveSessionFile,
125+
entries: [
126+
{
127+
type: "session",
128+
version: 3,
129+
id: "session-root-live",
130+
timestamp: "2026-04-18T04:20:00.000Z",
131+
cwd: "REDACTED_WORKDIR",
132+
},
133+
{
134+
type: "message",
135+
id: "msg-live-user",
136+
parentId: "session-root-live",
137+
timestamp: "2026-04-18T04:20:30.000Z",
138+
message: {
139+
role: "user",
140+
content: [{ type: "text", text: "[redacted live segment user]" }],
141+
},
142+
},
143+
{
144+
type: "message",
145+
id: "msg-live-assistant",
146+
parentId: "msg-live-user",
147+
timestamp: "2026-04-18T04:20:45.000Z",
148+
message: {
149+
role: "assistant",
150+
content: [{ type: "text", text: "[redacted live segment assistant]" }],
151+
},
152+
},
153+
],
154+
},
155+
];
156+
157+
const expectedLiveSegmentTexts = [
158+
"[redacted live segment user]",
159+
"[redacted live segment assistant]",
160+
];
161+
const expectedLiveSegmentMessageIds = ["msg-live-user", "msg-live-assistant"];
162+
163+
return {
164+
sessionId,
165+
liveSessionFile,
166+
transcripts,
167+
store: {
168+
"agent:main:main": {
169+
sessionId,
170+
sessionFile: liveSessionFile,
171+
compactionCheckpoints: checkpointChain,
172+
},
173+
},
174+
expectedTexts: [
175+
"[redacted chain segment 1 user]",
176+
"[redacted chain segment 1 assistant]",
177+
"[redacted chain segment 2 user]",
178+
"[redacted chain segment 2 assistant]",
179+
...expectedLiveSegmentTexts,
180+
],
181+
expectedMessageIds: [
182+
"msg-segment-1-user",
183+
"msg-segment-1-assistant",
184+
"msg-segment-2-user",
185+
"msg-segment-2-assistant",
186+
...expectedLiveSegmentMessageIds,
187+
],
188+
expectedLiveSegmentTexts,
189+
expectedLiveSegmentMessageIds,
190+
};
191+
}

src/gateway/session-utils.fs.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import path from "node:path";
44
import { afterAll, afterEach, beforeAll, describe, expect, test, vi } from "vitest";
55
import { clearSessionStoreCaches } from "../config/sessions/store-cache.js";
66
import { withTempHomeConfig } from "../config/test-helpers.js";
7+
import { createRepro68765CompactionChainFromProduction } from "./fixtures/repro-68765-compaction-chain-from-production.js";
78
import { createToolSummaryPreviewTranscriptLines } from "./session-preview.test-helpers.js";
89
import {
910
archiveSessionTranscripts,
@@ -50,6 +51,21 @@ function buildBasicSessionTranscript(
5051
];
5152
}
5253

54+
function extractMessageText(message: { content?: unknown }): string {
55+
if (typeof message.content === "string") {
56+
return message.content;
57+
}
58+
if (
59+
Array.isArray(message.content) &&
60+
message.content[0] &&
61+
typeof message.content[0] === "object"
62+
) {
63+
const first = message.content[0] as { text?: unknown };
64+
return typeof first.text === "string" ? first.text : "";
65+
}
66+
return "";
67+
}
68+
5369
describe("readFirstUserMessageFromTranscript", () => {
5470
let tmpDir: string;
5571
let storePath: string;
@@ -699,6 +715,42 @@ describe("readSessionMessages", () => {
699715
vi.unstubAllEnvs();
700716
clearSessionStoreCaches();
701717
});
718+
719+
test("reads a production-derived main-session checkpoint chain across predecessor transcripts", () => {
720+
const fixture = createRepro68765CompactionChainFromProduction(tmpDir);
721+
for (const transcript of fixture.transcripts) {
722+
fs.mkdirSync(path.dirname(transcript.filePath), { recursive: true });
723+
fs.writeFileSync(
724+
transcript.filePath,
725+
transcript.entries.map((entry) => JSON.stringify(entry)).join("\n"),
726+
"utf-8",
727+
);
728+
}
729+
fs.writeFileSync(storePath, JSON.stringify(fixture.store), "utf-8");
730+
731+
const truncated = readSessionMessages(
732+
fixture.sessionId,
733+
path.join(tmpDir, "wrong-sessions.json"),
734+
fixture.liveSessionFile,
735+
) as Array<{ content?: unknown; __openclaw?: { id?: string; seq?: number } }>;
736+
expect(truncated.map((message) => extractMessageText(message))).toEqual(
737+
fixture.expectedLiveSegmentTexts,
738+
);
739+
expect(truncated.map((message) => message.__openclaw?.id)).toEqual(
740+
fixture.expectedLiveSegmentMessageIds,
741+
);
742+
expect(truncated.map((message) => message.__openclaw?.seq)).toEqual([1, 2]);
743+
744+
const out = readSessionMessages(
745+
fixture.sessionId,
746+
storePath,
747+
fixture.liveSessionFile,
748+
) as Array<{ content?: unknown; __openclaw?: { id?: string; seq?: number } }>;
749+
750+
expect(out.map((message) => extractMessageText(message))).toEqual(fixture.expectedTexts);
751+
expect(out.map((message) => message.__openclaw?.id)).toEqual(fixture.expectedMessageIds);
752+
expect(out.map((message) => message.__openclaw?.seq)).toEqual([1, 2, 3, 4, 5, 6]);
753+
});
702754
});
703755

704756
// Red-bar fixtures for the compaction-chain epoch design decision that is still open

0 commit comments

Comments
 (0)