Files
PaperClipAI/tests/runner-e2e/journal-evidence.test.ts
DottaandPaperclip f4f9a7c613 test(runner): guard continuation after journals exceed 2 MiB (#14312)
Add an actual runner resume regression above the former 2 MiB journal boundary and an explicit-only three-turn Daytona workflow that grows real execution history. Verify journal size and distinct completed tool calls without exporting private payloads.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-09-30 07:01:45 -05:00

62 lines
4.0 KiB
TypeScript

import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
import { createHash } from "node:crypto";
import { tmpdir } from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
import { completedJournalStimulusCalls, largeJournalEvidence } from "./journal-evidence.js";
const output = `journal-continuity-${"x".repeat(65_000)}\n`;
function call(index: number) {
const event = { schema: "paperclip.prp.event.v1", runId: "expected-run", sourceKind: "runner", sourceInstanceId: "runner", normalizedSessionId: "session" };
const payload = { schema: "paperclip.tool.execution.v1", transport: "process", operation: "execute", executionId: `exec-${index}`, name: "python3 fixture" };
return [
{ ...event, eventType: "tool.execution.started", sourceSeq: index * 2, payload: { ...payload, status: "running" } },
{ ...event, eventType: "tool.execution.completed", sourceSeq: index * 2 + 1, payload: { ...payload, status: "completed", exitCode: 0, outputBytes: Buffer.byteLength(output), outputDigest: `sha256:${createHash("sha256").update(output).digest("hex")}` } },
];
}
describe("large journal boundary evidence", () => {
it("requires one correctly owned journal over the boundary and an independent completed-call count", async () => {
const root = await mkdtemp(path.join(tmpdir(), "journal-evidence-"));
const directory = path.join(root, "a".repeat(64), "control-plane");
await mkdir(directory, { recursive: true });
const filename = path.join(directory, "control-plane-state.json");
const state = { schema: "paperclip.runner.durable.control-plane-state.v1", identity: { runId: "expected-run" }, commands: [] };
const input = { stateRoot: root, runId: "expected-run", minimumBytes: 1024, minimumCompletedStimulusCalls: 2 };
try {
await writeFile(filename, JSON.stringify(state));
await expect(largeJournalEvidence(input)).rejects.toThrow("boundary not reached");
await writeFile(filename, JSON.stringify({ ...state, commands: [{ payload: "x".repeat(2048) }] }));
await expect(largeJournalEvidence(input)).rejects.toThrow("stimulus incomplete");
await writeFile(filename, JSON.stringify({ ...state, committedEvents: [...call(1), ...call(2)].map(event => ({ envelope: { payload: event } })) }));
const evidence = await largeJournalEvidence(input);
expect(Object.keys(evidence)).toEqual(["journalBytes", "minimumBytes", "completedStimulusCalls", "minimumCompletedStimulusCalls"]);
expect(evidence.completedStimulusCalls).toBe(2);
await expect(largeJournalEvidence({ ...input, runId: "another-run" })).rejects.toThrow("boundary not reached");
} finally { await rm(root, { recursive: true, force: true }); }
});
it("does not count repeated receipts, unmatched completion, wrong runs, failed or unrelated output", () => {
const pair = call(1);
expect(completedJournalStimulusCalls([...pair, ...pair], "expected-run")).toBe(1);
expect(completedJournalStimulusCalls([pair[1]], "expected-run")).toBe(0);
expect(completedJournalStimulusCalls(pair, "another-run")).toBe(0);
expect(completedJournalStimulusCalls([pair[0], { ...pair[1], payload: { ...pair[1]!.payload, exitCode: 1 } }], "expected-run")).toBe(0);
expect(completedJournalStimulusCalls([pair[0], { ...pair[1], payload: { ...pair[1]!.payload, outputDigest: "sha256:unrelated" } }], "expected-run")).toBe(0);
});
it("requires the same runner, session, execution and command with ordered source sequence", () => {
const [start, complete] = call(1);
for (const changed of [
{ ...complete, sourceInstanceId: "another-runner" },
{ ...complete, normalizedSessionId: "another-session" },
{ ...complete, sourceSeq: start!.sourceSeq },
{ ...complete, payload: { ...complete!.payload, executionId: "another-exec" } },
{ ...complete, payload: { ...complete!.payload, name: "another-command" } },
{ ...complete, payload: { ...complete!.payload, outputBytes: 1 } },
]) {
expect(completedJournalStimulusCalls([start, changed], "expected-run")).toBe(0);
}
});
});