Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/klaus/__tests__/cache.test.ts

Raw
import {
	chmod,
	mkdtemp,
	readdir,
	readFile,
	rm,
	stat,
	writeFile,
} from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import {
	FileSessionStore,
	KlausCacheCorruptionError,
	loadCheckpoint,
	loadCheckpoints,
	MemorySessionStore,
	type OpaqueEntry,
	StagingSessionStore,
	type StoreKey,
	saveCheckpoint,
} from "../src/cache";

const roots: string[] = [];
const key = { projectKey: "project", sessionId: "session" };

async function temporaryRoot(label: string): Promise<string> {
	const root = await mkdtemp(join(tmpdir(), `klaus-${label}-`));
	roots.push(root);
	return root;
}

async function onlyFile(root: string, suffix: string): Promise<string> {
	const files = (await readdir(root)).filter((file) => file.endsWith(suffix));
	expect(files).toHaveLength(1);
	const file = files[0];
	if (!file) throw new Error(`Missing ${suffix} fixture file.`);
	return join(root, file);
}

const checkpoint = (id: string, overrides = {}) => ({
	sdkSessionId: `sdk-${id}`,
	fingerprint: `fingerprint-${id}`,
	position: `position-${id}`,
	piLeafId: `leaf-${id}`,
	messageCount: 2,
	protocol: 1 as const,
	...overrides,
});

afterEach(async () => {
	await Promise.all(
		roots.splice(0).map((root) => rm(root, { recursive: true, force: true })),
	);
});

describe("Klaus in-memory opaque session store", () => {
	it("round-trips entries and ignores repeated UUIDs across and within batches", async () => {
		const store = new MemorySessionStore();
		await store.append(key, [
			{ type: "user", uuid: "one", opaque: { value: 1 } },
			{ type: "assistant", uuid: "one", ignored: true },
			{ type: "assistant", uuid: "two" },
		]);
		await store.append(key, [
			{ type: "user", uuid: "one", opaque: { value: 2 } },
		]);

		expect(await store.load(key)).toEqual([
			{ type: "user", uuid: "one", opaque: { value: 1 } },
			{ type: "assistant", uuid: "two" },
		]);
	});

	it("appends every UUID-less opaque entry", async () => {
		const store = new MemorySessionStore();
		const marker = { type: "mode", value: "same" };
		await store.append(key, [marker, marker]);
		await store.append(key, [marker]);

		expect(await store.load(key)).toEqual([marker, marker, marker]);
	});

	it("isolates project, session, and subpath key dimensions", async () => {
		const store = new MemorySessionStore();
		const keys: StoreKey[] = [
			key,
			{ ...key, projectKey: "other" },
			{ ...key, sessionId: "other" },
			{ ...key, subpath: "agent-a" },
			{ ...key, subpath: "agent-b" },
		];
		await Promise.all(
			keys.map((entryKey, index) =>
				store.append(entryKey, [{ type: "entry", index }]),
			),
		);

		for (const [index, entryKey] of keys.entries()) {
			expect(await store.load(entryKey)).toEqual([{ type: "entry", index }]);
		}
		expect(
			await store.load({ projectKey: "missing", sessionId: "missing" }),
		).toBeNull();
	});

	it("deep-clones appended and loaded values", async () => {
		const store = new MemorySessionStore();
		const source = { type: "entry", uuid: "one", nested: { value: 1 } };
		await store.append(key, [source]);
		source.nested.value = 2;
		const loaded = await store.load(key);
		if (!loaded) throw new Error("Missing memory entry.");
		const nested = loaded[0]?.nested as { value: number };
		nested.value = 3;

		expect(await store.load(key)).toEqual([
			{ type: "entry", uuid: "one", nested: { value: 1 } },
		]);
	});

	it.each([null, [], "entry", {}, { type: 1 }] as const)(
		"rejects invalid opaque entry %j",
		async (value) => {
			const store = new MemorySessionStore();
			await expect(
				store.append(key, [value as unknown as OpaqueEntry]),
			).rejects.toThrow("invalid opaque session entry");
			expect(await store.load(key)).toBeNull();
		},
	);

	it("reports no subkeys because opaque hashes are intentionally not reversible", async () => {
		const store = new MemorySessionStore();
		await store.append({ ...key, subpath: "agent" }, [{ type: "entry" }]);
		expect(await store.listSubkeys(key)).toEqual([]);
	});
});

describe("Klaus staging session store", () => {
	it("keeps query writes staged until commit", async () => {
		const target = new MemorySessionStore();
		const staging = new StagingSessionStore(target);
		await staging.append(key, [{ type: "user", uuid: "one" }]);

		expect(await target.load(key)).toBeNull();
		expect(await staging.load(key)).toEqual([{ type: "user", uuid: "one" }]);
		await staging.commit();
		expect(await target.load(key)).toEqual([{ type: "user", uuid: "one" }]);
	});

	it("combines committed and matching staged batches without leaking other keys", async () => {
		const target = new MemorySessionStore();
		await target.append(key, [{ type: "base", uuid: "base" }]);
		const staging = new StagingSessionStore(target);
		await staging.append(key, [{ type: "staged", uuid: "one" }]);
		await staging.append({ ...key, subpath: "other" }, [
			{ type: "other", uuid: "two" },
		]);

		expect(await staging.load(key)).toEqual([
			{ type: "base", uuid: "base" },
			{ type: "staged", uuid: "one" },
		]);
		expect(await staging.load({ ...key, subpath: "other" })).toEqual([
			{ type: "other", uuid: "two" },
		]);
	});

	it("deep-clones staged keys and entries", async () => {
		const target = new MemorySessionStore();
		const staging = new StagingSessionStore(target);
		const mutableKey: StoreKey = { ...key, subpath: "before" };
		const entry = { type: "entry", nested: { value: 1 } };
		await staging.append(mutableKey, [entry]);
		mutableKey.subpath = "after";
		entry.nested.value = 2;

		expect(await staging.load({ ...key, subpath: "before" })).toEqual([
			{ type: "entry", nested: { value: 1 } },
		]);
	});

	it("commits each batch once even when commit is repeated", async () => {
		const target = new MemorySessionStore();
		const staging = new StagingSessionStore(target);
		await staging.append(key, [{ type: "marker" }]);
		await staging.commit();
		await staging.commit();

		expect(await target.load(key)).toEqual([{ type: "marker" }]);
	});
});

describe("Klaus disk opaque session store", () => {
	it("creates a pinned manifest and round-trips opaque NDJSON", async () => {
		const parent = await temporaryRoot("cache");
		const root = join(parent, "sessions");
		const store = new FileSessionStore(root);
		await store.append(key, [
			{ type: "assistant", uuid: "one", payload: "opaque" },
		]);

		expect(await store.load(key)).toEqual([
			{ type: "assistant", uuid: "one", payload: "opaque" },
		]);
		expect(
			JSON.parse(await readFile(join(root, "manifest.json"), "utf8")),
		).toEqual({ schema: 1, sdk: "0.3.281" });
		const transcript = await onlyFile(root, ".ndjson");
		expect(await readFile(transcript, "utf8")).toBe(
			'{"type":"assistant","uuid":"one","payload":"opaque"}\n',
		);
	});

	it.runIf(process.platform !== "win32")(
		"uses private directories, manifests, and transcript files",
		async () => {
			const parent = await temporaryRoot("permissions");
			const root = join(parent, "sessions");
			const store = new FileSessionStore(root);
			await store.append(key, [{ type: "assistant", uuid: "one" }]);

			expect((await stat(root)).mode & 0o777).toBe(0o700);
			expect((await stat(join(root, "manifest.json"))).mode & 0o777).toBe(
				0o600,
			);
			expect((await stat(await onlyFile(root, ".ndjson"))).mode & 0o777).toBe(
				0o600,
			);
		},
	);

	it("deduplicates UUIDs but preserves UUID-less records on disk", async () => {
		const root = join(await temporaryRoot("dedup"), "sessions");
		const store = new FileSessionStore(root);
		await store.append(key, [
			{ type: "entry", uuid: "one", value: 1 },
			{ type: "entry", uuid: "one", value: 2 },
			{ type: "marker", value: 1 },
		]);
		await store.append(key, [
			{ type: "entry", uuid: "one", value: 3 },
			{ type: "marker", value: 1 },
		]);

		expect(await store.load(key)).toEqual([
			{ type: "entry", uuid: "one", value: 1 },
			{ type: "marker", value: 1 },
			{ type: "marker", value: 1 },
		]);
	});

	it("serializes concurrent appends for one transcript without loss", async () => {
		const root = join(await temporaryRoot("concurrent-cache"), "sessions");
		const store = new FileSessionStore(root);
		await Promise.all(
			Array.from({ length: 40 }, (_, index) =>
				store.append(key, [{ type: "entry", uuid: `uuid-${index}`, index }]),
			),
		);
		const loaded = await store.load(key);

		expect(loaded).toHaveLength(40);
		expect(loaded?.map((entry) => entry.uuid).toSorted()).toEqual(
			Array.from({ length: 40 }, (_, index) => `uuid-${index}`).toSorted(),
		);
	});

	it("keeps concurrent transcripts isolated by key", async () => {
		const root = join(await temporaryRoot("parallel-keys"), "sessions");
		const store = new FileSessionStore(root);
		const left = { ...key, sessionId: "left" };
		const right = { ...key, sessionId: "right" };
		await Promise.all([
			store.append(left, [{ type: "entry", value: "left" }]),
			store.append(right, [{ type: "entry", value: "right" }]),
		]);

		expect(await store.load(left)).toEqual([{ type: "entry", value: "left" }]);
		expect(await store.load(right)).toEqual([
			{ type: "entry", value: "right" },
		]);
		expect(
			(await readdir(root)).filter((file) => file.endsWith(".ndjson")),
		).toHaveLength(2);
	});

	it("does not partially append a batch containing an invalid entry", async () => {
		const root = join(await temporaryRoot("invalid-batch"), "sessions");
		const store = new FileSessionStore(root);
		await expect(
			store.append(key, [
				{ type: "valid", uuid: "one" },
				null as unknown as OpaqueEntry,
			]),
		).rejects.toThrow("invalid opaque session entry");
		expect(await store.load(key)).toBeNull();
	});

	it("fails closed on malformed transcript JSON or entry shape", async () => {
		for (const text of ["not-json\n", '{"uuid":"missing-type"}\n']) {
			const root = join(await temporaryRoot("corrupt-transcript"), "sessions");
			const store = new FileSessionStore(root);
			await store.append(key, [{ type: "valid" }]);
			const transcript = await onlyFile(root, ".ndjson");
			await writeFile(transcript, text);
			await expect(store.load(key)).rejects.toBeInstanceOf(
				KlausCacheCorruptionError,
			);
			expect(await readFile(transcript, "utf8")).toBe(text);
		}
	});

	it.each([
		{ schema: 2, sdk: "0.3.281" },
		{ schema: 1, sdk: "0.3.238" },
		{},
		null,
	] as const)("rejects incompatible manifest %j", async (manifest) => {
		const root = join(await temporaryRoot("manifest"), "sessions");
		await import("node:fs/promises").then(({ mkdir }) => mkdir(root));
		await writeFile(join(root, "manifest.json"), JSON.stringify(manifest));
		const store = new FileSessionStore(root);

		await expect(store.prepare()).rejects.toThrow(
			"Klaus cache requires a tested migration",
		);
	});

	it("rejects a nonempty cache root without a manifest", async () => {
		const root = join(await temporaryRoot("missing-manifest"), "sessions");
		await import("node:fs/promises").then(async ({ mkdir }) => {
			await mkdir(root);
			await writeFile(join(root, "unknown"), "data");
		});

		await expect(new FileSessionStore(root).prepare()).rejects.toThrow(
			"Klaus cache requires a tested migration",
		);
	});

	it("makes prepare idempotent", async () => {
		const root = join(await temporaryRoot("prepare"), "sessions");
		const store = new FileSessionStore(root);
		await Promise.all([store.prepare(), store.prepare(), store.prepare()]);
		expect(await readdir(root)).toEqual(["manifest.json"]);
	});

	it("returns no subkeys because persisted names are opaque hashes", async () => {
		const root = join(await temporaryRoot("subkeys"), "sessions");
		const store = new FileSessionStore(root);
		await store.append({ ...key, subpath: "agent" }, [{ type: "entry" }]);
		expect(await store.listSubkeys(key)).toEqual([]);
	});
});

describe("Klaus checkpoints", () => {
	it("returns an empty lineage for a missing checkpoint file", async () => {
		const root = join(await temporaryRoot("missing-checkpoint"), "checkpoints");
		expect(await loadCheckpoints(root, "missing")).toEqual([]);
		expect(await loadCheckpoint(root, "missing")).toBeUndefined();
	});

	it("atomically round-trips private checkpoint arrays", async () => {
		const root = join(await temporaryRoot("checkpoint"), "checkpoints");
		await saveCheckpoint(root, "pi-session", checkpoint("one"));
		await saveCheckpoint(root, "pi-session", checkpoint("two"));

		expect(await loadCheckpoints(root, "pi-session")).toEqual([
			checkpoint("one"),
			checkpoint("two"),
		]);
		expect(await loadCheckpoint(root, "pi-session")).toEqual(checkpoint("two"));
		const files = await readdir(root);
		expect(files.some((file) => file.endsWith(".tmp"))).toBe(false);
	});

	it.runIf(process.platform !== "win32")(
		"writes checkpoint directories and files privately",
		async () => {
			const root = join(await temporaryRoot("checkpoint-mode"), "checkpoints");
			await saveCheckpoint(root, "pi-session", checkpoint("one"));
			expect((await stat(root)).mode & 0o777).toBe(0o700);
			expect((await stat(await onlyFile(root, ".json"))).mode & 0o777).toBe(
				0o600,
			);
		},
	);

	it("loads the legacy single-checkpoint representation", async () => {
		const root = join(await temporaryRoot("legacy-checkpoint"), "checkpoints");
		await saveCheckpoint(root, "pi-session", checkpoint("one"));
		const path = await onlyFile(root, ".json");
		await writeFile(path, JSON.stringify(checkpoint("legacy")));

		expect(await loadCheckpoints(root, "pi-session")).toEqual([
			checkpoint("legacy"),
		]);
	});

	it("replaces the same leaf and fingerprint instead of growing lineage", async () => {
		const root = join(await temporaryRoot("replace-checkpoint"), "checkpoints");
		const first = checkpoint("same");
		const replacement = { ...first, sdkSessionId: "sdk-new", messageCount: 9 };
		await saveCheckpoint(root, "pi-session", first);
		await saveCheckpoint(root, "pi-session", replacement);

		expect(await loadCheckpoints(root, "pi-session")).toEqual([replacement]);
	});

	it("keeps distinct fingerprints for one leaf and distinct leaves for one fingerprint", async () => {
		const root = join(await temporaryRoot("lineage-checkpoint"), "checkpoints");
		const first = checkpoint("first", { piLeafId: "shared-leaf" });
		const second = checkpoint("second", { piLeafId: "shared-leaf" });
		const third = checkpoint("third", {
			fingerprint: second.fingerprint,
			piLeafId: "other-leaf",
		});
		await saveCheckpoint(root, "pi-session", first);
		await saveCheckpoint(root, "pi-session", second);
		await saveCheckpoint(root, "pi-session", third);

		expect(await loadCheckpoints(root, "pi-session")).toEqual([
			first,
			second,
			third,
		]);
	});

	it("isolates checkpoint files by Pi session ID", async () => {
		const root = join(
			await temporaryRoot("checkpoint-isolation"),
			"checkpoints",
		);
		await Promise.all([
			saveCheckpoint(root, "session-a", checkpoint("a")),
			saveCheckpoint(root, "session-b", checkpoint("b")),
		]);

		expect(await loadCheckpoint(root, "session-a")).toEqual(checkpoint("a"));
		expect(await loadCheckpoint(root, "session-b")).toEqual(checkpoint("b"));
		expect(
			(await readdir(root)).filter((file) => file.endsWith(".json")),
		).toHaveLength(2);
	});

	it("serializes concurrent lineage writes without losing checkpoints", async () => {
		const root = join(
			await temporaryRoot("concurrent-checkpoints"),
			"checkpoints",
		);
		const expected = Array.from({ length: 30 }, (_, index) =>
			checkpoint(String(index)),
		);
		await Promise.all(
			expected.map((item) => saveCheckpoint(root, "pi-session", item)),
		);

		expect(await loadCheckpoints(root, "pi-session")).toEqual(expected);
	});

	it.each([
		null,
		{},
		{ sdkSessionId: 1, fingerprint: "f", messageCount: 1, protocol: 1 },
		{ sdkSessionId: "s", fingerprint: 1, messageCount: 1, protocol: 1 },
		{ sdkSessionId: "s", fingerprint: "f", messageCount: 1.5, protocol: 1 },
		{ sdkSessionId: "s", fingerprint: "f", messageCount: -1, protocol: 1 },
		{ sdkSessionId: "s", fingerprint: "f", messageCount: 1, protocol: 2 },
		{
			sdkSessionId: "s",
			fingerprint: "f",
			messageCount: 1,
			protocol: 1,
			position: 1,
		},
		{
			sdkSessionId: "s",
			fingerprint: "f",
			messageCount: 1,
			protocol: 1,
			piLeafId: 1,
		},
	] as const)("rejects invalid checkpoint %j", async (value) => {
		const root = join(await temporaryRoot("invalid-checkpoint"), "checkpoints");
		await saveCheckpoint(root, "pi-session", checkpoint("valid"));
		await writeFile(await onlyFile(root, ".json"), JSON.stringify(value));

		await expect(loadCheckpoints(root, "pi-session")).rejects.toThrow(
			"Klaus checkpoint is invalid",
		);
	});

	it("rejects malformed checkpoint JSON", async () => {
		const root = join(
			await temporaryRoot("malformed-checkpoint"),
			"checkpoints",
		);
		await saveCheckpoint(root, "pi-session", checkpoint("valid"));
		await writeFile(await onlyFile(root, ".json"), "{");
		await expect(loadCheckpoint(root, "pi-session")).rejects.toThrow();
	});

	it.runIf(process.platform !== "win32")(
		"replaces an insecure checkpoint file with mode 0600",
		async () => {
			const root = join(
				await temporaryRoot("checkpoint-replace-mode"),
				"checkpoints",
			);
			await saveCheckpoint(root, "pi-session", checkpoint("one"));
			const path = await onlyFile(root, ".json");
			await chmod(path, 0o644);
			await saveCheckpoint(root, "pi-session", checkpoint("two"));
			expect((await stat(path)).mode & 0o777).toBe(0o600);
		},
	);
});