chore(vendor): import pi 0.85.1

Imported as a linear vendor commit so forge rebases do not replay the upstream root tree.

git-subtree-dir: third_party/pi

git-subtree-split: d981de1229ef899957bbe968bc8dcda02a21f477
This commit is contained in:
2026-09-14 10:48:47 -04:00
parent 5ebc7018c3
commit 4def357d09
1669 changed files with 397627 additions and 0 deletions

View File

@@ -0,0 +1,102 @@
import { access, mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { createNodeSqliteFactory } from "../src/index.ts";
async function withTempDir<T>(run: (directory: string) => Promise<T>): Promise<T> {
const directory = await mkdtemp(join(tmpdir(), "pi-sqlite-adapter-"));
try {
return await run(directory);
} finally {
await rm(directory, { recursive: true, force: true });
}
}
describe("node:sqlite adapter", () => {
it("does not create files for existing or read-only opens", async () => {
await withTempDir(async (directory) => {
const path = join(directory, "missing % #.sqlite");
const factory = createNodeSqliteFactory();
await expect(factory.openExisting(path)).rejects.toThrow();
await expect(access(path)).rejects.toThrow();
await expect(factory.openReadOnly(path)).rejects.toThrow();
await expect(access(path)).rejects.toThrow();
});
});
it("opens an existing database read-write or read-only without changing its mode", async () => {
await withTempDir(async (directory) => {
const path = join(directory, "existing % #.sqlite");
const factory = createNodeSqliteFactory();
const created = await factory.open(path);
created.exec("CREATE TABLE values_table (value INTEGER NOT NULL)");
created.close();
const writable = await factory.openExisting(path);
writable.exec("INSERT INTO values_table (value) VALUES (1)");
writable.close();
const readOnly = await factory.openReadOnly(path);
try {
expect(readOnly.prepare("SELECT value FROM values_table").all()).toEqual([{ value: 1 }]);
expect(() => readOnly.exec("INSERT INTO values_table (value) VALUES (2)")).toThrow();
} finally {
readOnly.close();
}
});
});
it("commits a synchronous transaction and returns its result", async () => {
const db = await createNodeSqliteFactory().open(":memory:");
try {
db.exec("CREATE TABLE values_table (value INTEGER NOT NULL)");
const result = db.transaction(() => {
db.prepare("INSERT INTO values_table (value) VALUES (?)").run(42);
return "committed";
});
expect(result).toBe("committed");
expect(db.prepare("SELECT value FROM values_table").get()).toEqual({ value: 42 });
} finally {
db.close();
}
});
it("forwards positional and named statement parameters", async () => {
const db = await createNodeSqliteFactory().open(":memory:");
try {
db.exec("CREATE TABLE values_table (id INTEGER PRIMARY KEY, value TEXT NOT NULL)");
expect(db.prepare("INSERT INTO values_table (value) VALUES (?)").run("positional")).toEqual({
changes: 1,
lastInsertRowid: 1,
});
expect(db.prepare("INSERT INTO values_table (value) VALUES (:value)").run({ value: "named" })).toEqual({
changes: 1,
lastInsertRowid: 2,
});
expect(db.prepare("SELECT value FROM values_table WHERE id = ?").get(1)).toEqual({ value: "positional" });
expect(db.prepare("SELECT value FROM values_table WHERE id >= :id ORDER BY id").all({ id: 2 })).toEqual([
{ value: "named" },
]);
} finally {
db.close();
}
});
it("rejects asynchronous transaction callbacks", async () => {
const db = await createNodeSqliteFactory().open(":memory:");
try {
db.exec("CREATE TABLE values_table (value INTEGER NOT NULL)");
const asynchronous = async () => {
db.prepare("INSERT INTO values_table (value) VALUES (?)").run(42);
await Promise.resolve();
};
expect(() => db.transaction(asynchronous)).toThrow("SQLite transaction callbacks must be synchronous");
await Promise.resolve();
expect(db.prepare("SELECT value FROM values_table").all()).toEqual([]);
} finally {
db.close();
}
});
});

View File

@@ -0,0 +1,67 @@
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import {
type ConformanceCase,
createSessionRepoConformance,
} from "@earendil-works/pi-agent-core/harness/session/testing";
import { describe, it } from "vitest";
import { createNodeSqliteFactory, SqliteSessionRepo } from "../src/index.ts";
const NOW = 1_700_000_000_000;
function registerConformance(name: string, cases: readonly ConformanceCase[]): void {
describe(name, () => {
for (const group of new Set(cases.map((testCase) => testCase.group))) {
describe(group, () => {
for (const testCase of cases.filter((candidate) => candidate.group === group)) {
it(testCase.name, () => testCase.run());
}
});
}
});
}
let currentDirectory: string | undefined;
let currentSharedDirectory: string | undefined;
async function createConformanceRepo() {
currentDirectory = await mkdtemp(join(tmpdir(), "pi-sqlite-session-repo-conformance-"));
return new SqliteSessionRepo({
directory: currentDirectory,
databaseFactory: createNodeSqliteFactory(),
now: () => NOW,
});
}
async function createSharedContainerConformanceRepo() {
currentSharedDirectory = await mkdtemp(join(tmpdir(), "pi-sqlite-session-repo-shared-conformance-"));
return new SqliteSessionRepo({
directory: currentSharedDirectory,
databasePath: join(currentSharedDirectory, "sessions.sqlite"),
databaseFactory: createNodeSqliteFactory(),
now: () => NOW,
});
}
async function cleanupConformanceRepo() {
if (currentDirectory === undefined) return;
await rm(currentDirectory, { recursive: true, force: true });
currentDirectory = undefined;
}
async function cleanupSharedContainerConformanceRepo() {
if (currentSharedDirectory === undefined) return;
await rm(currentSharedDirectory, { recursive: true, force: true });
currentSharedDirectory = undefined;
}
registerConformance(
"SqliteSessionRepo conformance",
createSessionRepoConformance(createConformanceRepo, cleanupConformanceRepo),
);
registerConformance(
"SqliteSessionRepo shared-container conformance",
createSessionRepoConformance(createSharedContainerConformanceRepo, cleanupSharedContainerConformanceRepo),
);

View File

@@ -0,0 +1,944 @@
import { access, mkdtemp, realpath, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, isAbsolute, join, relative } from "node:path";
import * as storedValues from "@earendil-works/pi-agent-core";
import * as sessionWrites from "@earendil-works/pi-agent-core";
import { BACKGROUND_CONTEXT } from "@earendil-works/pi-agent-core";
import { describe, expect, it } from "vitest";
import type { SqliteDatabase, SqliteDatabaseFactory, SqliteStatement } from "../src/index.ts";
import { createNodeSqliteFactory, SqliteSessionRepo, sql } from "../src/index.ts";
const TEST_LANE_CONFIGURATION = {
model: { provider: "test", modelId: "test" },
thinkingLevel: "off",
activeToolNames: [],
} satisfies storedValues.LaneConfiguration;
const IDLE_LANE_STATE = {
currentOperationId: null,
lastOperationId: null,
inbox: [],
} satisfies storedValues.LaneState;
async function withTempDir<T>(run: (directory: string) => Promise<T>): Promise<T> {
const directory = await mkdtemp(join(tmpdir(), "pi-sqlite-session-"));
try {
return await run(directory);
} finally {
await rm(directory, { recursive: true, force: true });
}
}
async function withDb<T>(path: string, run: (db: SqliteDatabase) => Promise<T> | T): Promise<T> {
const db = await createNodeSqliteFactory().open(path);
try {
return await run(db);
} finally {
db.close();
}
}
async function pathExists(path: string): Promise<boolean> {
return access(path).then(
() => true,
() => false,
);
}
class ForwardingDatabase implements SqliteDatabase {
readonly source: SqliteDatabase;
constructor(source: SqliteDatabase) {
this.source = source;
}
exec(query: string): void {
this.source.exec(query);
}
prepare(query: string): SqliteStatement {
return this.source.prepare(query);
}
transaction<T>(callback: () => T): T {
return this.source.transaction(callback);
}
close(): void {
this.source.close();
}
}
class SnapshotBoundaryStatement implements SqliteStatement {
readonly source: SqliteStatement;
readonly afterGet: () => void;
private called = false;
constructor(source: SqliteStatement, afterGet: () => void) {
this.source = source;
this.afterGet = afterGet;
}
run(...params: unknown[]) {
return this.source.run(...params);
}
get<TRow extends object>(...params: unknown[]): TRow | undefined {
const row = this.source.get<TRow>(...params);
if (!this.called) {
this.called = true;
this.afterGet();
}
return row;
}
all<TRow extends object>(...params: unknown[]): TRow[] {
return this.source.all<TRow>(...params);
}
iterate<TRow extends object>(...params: unknown[]): Iterable<TRow> {
return this.source.iterate<TRow>(...params);
}
}
class SnapshotBoundaryDatabase extends ForwardingDatabase {
readonly afterSnapshotEstablished: () => void;
constructor(source: SqliteDatabase, afterSnapshotEstablished: () => void) {
super(source);
this.afterSnapshotEstablished = afterSnapshotEstablished;
}
override prepare(query: string): SqliteStatement {
const statement = this.source.prepare(query);
return query.includes("FROM sessions")
? new SnapshotBoundaryStatement(statement, this.afterSnapshotEstablished)
: statement;
}
}
class SnapshotBoundaryFactory implements SqliteDatabaseFactory {
readonly source = createNodeSqliteFactory();
readonly afterSnapshotEstablished: () => void;
readOnlyOpenCount = 0;
constructor(afterSnapshotEstablished: () => void) {
this.afterSnapshotEstablished = afterSnapshotEstablished;
}
open(path: string): Promise<SqliteDatabase> {
return this.source.open(path);
}
openExisting(path: string): Promise<SqliteDatabase> {
return this.source.openExisting(path);
}
async openReadOnly(path: string): Promise<SqliteDatabase> {
this.readOnlyOpenCount++;
return new SnapshotBoundaryDatabase(await this.source.openReadOnly(path), this.afterSnapshotEstablished);
}
}
function deferred(): { promise: Promise<void>; resolve: () => void } {
let resolve!: () => void;
const promise = new Promise<void>((resolvePromise) => {
resolve = resolvePromise;
});
return { promise, resolve };
}
class GatedOpenExistingFactory implements SqliteDatabaseFactory {
readonly source = createNodeSqliteFactory();
readonly entered = deferred();
readonly release = deferred();
private gated = false;
arm(): void {
this.gated = true;
}
open(path: string): Promise<SqliteDatabase> {
return this.source.open(path);
}
async openExisting(path: string): Promise<SqliteDatabase> {
if (this.gated) {
this.gated = false;
this.entered.resolve();
await this.release.promise;
}
return this.source.openExisting(path);
}
openReadOnly(path: string): Promise<SqliteDatabase> {
return this.source.openReadOnly(path);
}
}
class CloseTrackingDatabase extends ForwardingDatabase {
closeAttempts = 0;
closeError: Error | undefined;
override close(): void {
this.closeAttempts++;
this.source.close();
if (this.closeError !== undefined) throw this.closeError;
}
}
class CloseTrackingFactory implements SqliteDatabaseFactory {
readonly source = createNodeSqliteFactory();
readonly writableConnections: CloseTrackingDatabase[] = [];
async open(path: string): Promise<SqliteDatabase> {
const db = new CloseTrackingDatabase(await this.source.open(path));
this.writableConnections.push(db);
return db;
}
async openExisting(path: string): Promise<SqliteDatabase> {
const db = new CloseTrackingDatabase(await this.source.openExisting(path));
this.writableConnections.push(db);
return db;
}
openReadOnly(path: string): Promise<SqliteDatabase> {
return this.source.openReadOnly(path);
}
}
function commitLaterSourceState(db: SqliteDatabase): void {
const tip = storedValues.branchTip("main");
const name = storedValues.sessionName;
const label = storedValues.entryLabel("root");
db.transaction(() => {
sql`INSERT INTO entries (session_id, id, parent_id, seq, type, custom_type, timestamp, payload)
VALUES (${"source"}, ${"child"}, ${"root"}, ${7}, ${"message"}, ${null}, ${2}, ${JSON.stringify({ message: { role: "user", content: "after", timestamp: 2 } })})`.run(
db,
);
sql`INSERT INTO branch_entries (session_id, branch_id, entry_id, entry_seq, entry_type)
VALUES (${"source"}, ${"root"}, ${"child"}, ${7}, ${"message"})`.run(db);
sql`UPDATE branch_meta SET tip_entry_id = ${"child"}, tip_seq = ${7}
WHERE session_id = ${"source"} AND branch_id = ${"root"}`.run(db);
for (const [namespace, key, seq, value] of [
[tip.namespace, tip.key, 8, "child"],
[name.namespace, name.key, 9, "after"],
[label.namespace, label.key, 10, "after-label"],
] as const) {
sql`INSERT INTO scalar_values (session_id, namespace, key, seq, value)
VALUES (${"source"}, ${namespace}, ${key}, ${seq}, ${JSON.stringify(value)})
ON CONFLICT(session_id, namespace, key) DO UPDATE SET seq = excluded.seq, value = excluded.value`.run(db);
}
sql`UPDATE sessions SET message_count = ${1}, next_seq = ${11} WHERE id = ${"source"}`.run(db);
});
}
describe("SqliteSessionRepo", () => {
it("creates one branchless initialized session file", async () => {
await withTempDir(async (directory) => {
const repo = new SqliteSessionRepo({
directory,
databaseFactory: createNodeSqliteFactory(),
now: () => 1_700_000_000_000,
});
const session = await repo.create({ id: "session" }, BACKGROUND_CONTEXT);
const metadata = session.metadata;
expect(metadata).toMatchObject({
id: "session",
createdAt: 1_700_000_000_000,
storageVersion: 1,
});
expect(metadata.path).toBe(await realpath(join(directory, "session.sqlite")));
await withDb(metadata.path, (db) => {
expect(sql`SELECT COUNT(*) AS count FROM sessions`.get<{ count: number }>(db)).toEqual({ count: 1 });
expect(
sql`SELECT message_count, usage_payload, next_seq FROM sessions WHERE id = ${"session"}`.get(db),
).toEqual({
message_count: 0,
usage_payload: JSON.stringify({
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
}),
next_seq: 1,
});
expect(
sql`SELECT namespace, key, seq, value FROM scalar_values WHERE session_id = ${"session"} ORDER BY seq`.all(
db,
),
).toEqual([]);
expect(sql`SELECT COUNT(*) AS count FROM list_values WHERE session_id = ${"session"}`.get(db)).toEqual({
count: 0,
});
});
await session.close(BACKGROUND_CONTEXT);
});
});
it("exposes explicit branch scans through the open-session facade", async () => {
await withTempDir(async (directory) => {
const repo = new SqliteSessionRepo({
directory,
databaseFactory: createNodeSqliteFactory(),
now: () => 1_700_000_000_000,
});
const session = await repo.create({ id: "session" }, BACKGROUND_CONTEXT);
await session.mutate(
(mutator) =>
mutator.commit(
[
sessionWrites.insertEntry({ id: "root", parentId: null, type: "custom", customType: "root" }),
sessionWrites.insertEntry({ id: "child", parentId: "root", type: "custom", customType: "child" }),
],
BACKGROUND_CONTEXT,
),
BACKGROUND_CONTEXT,
);
expect(await session.scanBranch({ start: "child", order: "oldestFirst" }, BACKGROUND_CONTEXT)).toMatchObject([
{ id: "root" },
{ id: "child" },
]);
await session.close(BACKGROUND_CONTEXT);
await expect(session.scanBranch({ start: "child" }, BACKGROUND_CONTEXT)).rejects.toThrow("Session is closed");
});
});
it("commits an explicit mutation through the open-session facade", async () => {
await withTempDir(async (directory) => {
const repo = new SqliteSessionRepo({
directory,
databaseFactory: createNodeSqliteFactory(),
now: () => 1_700_000_000_000,
});
const session = await repo.create({ id: "session" }, BACKGROUND_CONTEXT);
const mutation = await session.beginMutation(BACKGROUND_CONTEXT);
const result = await mutation.commit(
[storedValues.setValue(storedValues.sessionName, "explicit")],
BACKGROUND_CONTEXT,
);
expect(result.seqs).toHaveLength(1);
expect(await mutation.getValue(storedValues.sessionName, BACKGROUND_CONTEXT)).toMatchObject({
value: "explicit",
});
await mutation.end(BACKGROUND_CONTEXT);
expect(await session.getName(BACKGROUND_CONTEXT)).toBe("explicit");
await session.close(BACKGROUND_CONTEXT);
});
});
it("rejects duplicate create without deleting the existing database", async () => {
await withTempDir(async (directory) => {
const repo = new SqliteSessionRepo({
directory,
databaseFactory: createNodeSqliteFactory(),
now: () => 1,
});
const session = await repo.create({ id: "session" }, BACKGROUND_CONTEXT);
const { metadata } = session;
await expect(repo.create({ id: "session" }, BACKGROUND_CONTEXT)).rejects.toThrow();
await withDb(metadata.path, (db) => {
expect(sql`SELECT COUNT(*) AS count FROM sessions`.get<{ count: number }>(db)).toEqual({ count: 1 });
});
await session.close(BACKGROUND_CONTEXT);
});
});
it("lists an open session without storage-layer ownership state", async () => {
await withTempDir(async (directory) => {
const repo = new SqliteSessionRepo({
directory,
databaseFactory: createNodeSqliteFactory(),
now: () => 1,
});
const session = await repo.create({ id: "session" }, BACKGROUND_CONTEXT);
const { metadata } = session;
await expect(repo.list(undefined, BACKGROUND_CONTEXT)).resolves.toMatchObject([
{ id: "session", path: metadata.path },
]);
await withDb(metadata.path, (db) => {
expect(
sql`SELECT name FROM sqlite_master WHERE type = ${"table"} AND name = ${"writer_lease"}`.get(db),
).toBeUndefined();
});
await session.close(BACKGROUND_CONTEXT);
});
});
it("ignores a stale writer_lease table from a pre-WP07 WIP database", async () => {
await withTempDir(async (directory) => {
const repo = new SqliteSessionRepo({
directory,
databaseFactory: createNodeSqliteFactory(),
now: () => 1,
});
const created = await repo.create({ id: "session" }, BACKGROUND_CONTEXT);
await created.close(BACKGROUND_CONTEXT);
await withDb(created.metadata.path, (db) => {
db.exec(
"CREATE TABLE writer_lease (session_id TEXT PRIMARY KEY, owner_id TEXT NOT NULL, fence INTEGER NOT NULL, expires_at_ms INTEGER NOT NULL) WITHOUT ROWID",
);
sql`INSERT INTO writer_lease (session_id, owner_id, fence, expires_at_ms)
VALUES (${"session"}, ${"stale"}, ${7}, ${999})`.run(db);
});
const reopened = await repo.open(created.metadata, BACKGROUND_CONTEXT);
await reopened.setName("works", BACKGROUND_CONTEXT);
await reopened.close(BACKGROUND_CONTEXT);
await withDb(created.metadata.path, (db) => {
expect(sql`SELECT owner_id, fence FROM writer_lease WHERE session_id = ${"session"}`.get(db)).toEqual({
owner_id: "stale",
fence: 7,
});
});
});
});
it("skips corrupt and incompatible files during list discovery", async () => {
await withTempDir(async (directory) => {
const repo = new SqliteSessionRepo({
directory,
databaseFactory: createNodeSqliteFactory(),
now: () => 1,
});
const session = await repo.create({ id: "session" }, BACKGROUND_CONTEXT);
await session.close(BACKGROUND_CONTEXT);
await writeFile(join(directory, "corrupt.sqlite"), "not a sqlite database");
await withDb(session.metadata.path, (db) => {
sql`UPDATE sessions SET storage_version = ${999} WHERE id = ${"session"}`.run(db);
});
expect(await repo.list(undefined, BACKGROUND_CONTEXT)).toEqual([]);
});
});
it("does not remove a pre-existing non-database file when create fails", async () => {
await withTempDir(async (directory) => {
const repo = new SqliteSessionRepo({
directory,
databaseFactory: createNodeSqliteFactory(),
now: () => 1,
});
const path = join(directory, "session.sqlite");
await writeFile(path, "not a sqlite database");
await expect(repo.create({ id: "session" }, BACKGROUND_CONTEXT)).rejects.toThrow();
await expect(access(path)).resolves.toBeUndefined();
});
});
it("rejects delete for missing files and deletes a closed session", async () => {
await withTempDir(async (directory) => {
const repo = new SqliteSessionRepo({
directory,
databaseFactory: createNodeSqliteFactory(),
now: () => 1,
});
const session = await repo.create({ id: "session" }, BACKGROUND_CONTEXT);
const { metadata } = session;
await session.close(BACKGROUND_CONTEXT);
await expect(
repo.delete({ ...metadata, path: join(directory, "missing.sqlite") }, BACKGROUND_CONTEXT),
).rejects.toThrow();
await repo.delete(metadata, BACKGROUND_CONTEXT);
await expect(access(metadata.path)).rejects.toThrow();
});
});
it("closes open sessions through repo close and rejects later operations", async () => {
await withTempDir(async (directory) => {
const repo = new SqliteSessionRepo({
directory,
databaseFactory: createNodeSqliteFactory(),
now: () => 1,
});
const session = await repo.create({ id: "session" }, BACKGROUND_CONTEXT);
await repo.close(BACKGROUND_CONTEXT);
await expect(session.getStats(BACKGROUND_CONTEXT)).rejects.toThrow("Session is closed");
await expect(repo.list(undefined, BACKGROUND_CONTEXT)).rejects.toThrow("SqliteSessionRepo is closed");
await expect(repo.create({ id: "other" }, BACKGROUND_CONTEXT)).rejects.toThrow("SqliteSessionRepo is closed");
await expect(repo.close(BACKGROUND_CONTEXT)).resolves.toBeUndefined();
});
});
it("opens a session through the version gate and rejects a duplicate local handle", async () => {
await withTempDir(async (directory) => {
const repo = new SqliteSessionRepo({
directory,
databaseFactory: createNodeSqliteFactory(),
now: () => 1,
});
const created = await repo.create({ id: "session" }, BACKGROUND_CONTEXT);
const { metadata } = created;
await created.close(BACKGROUND_CONTEXT);
const opened = await repo.open(metadata, BACKGROUND_CONTEXT);
expect(opened.metadata).toMatchObject({ id: "session", path: metadata.path });
await expect(repo.open(metadata, BACKGROUND_CONTEXT)).rejects.toThrow("already open");
await opened.close(BACKGROUND_CONTEXT);
});
});
it("isolates sessions stored in one shared SQLite container", async () => {
await withTempDir(async (directory) => {
const databasePath = join(directory, "sessions.sqlite");
const repo = new SqliteSessionRepo({
directory,
databasePath,
databaseFactory: createNodeSqliteFactory(),
now: () => 1_700_000_000_000,
});
const left = await repo.create({ id: "left" }, BACKGROUND_CONTEXT);
const right = await repo.create({ id: "right" }, BACKGROUND_CONTEXT);
expect(left.metadata.path).toBe(await realpath(databasePath));
expect(right.metadata.path).toBe(await realpath(databasePath));
await left.mutate(
(mutator) =>
mutator.commit(
[
sessionWrites.insertEntry({ id: "left-root", parentId: null, type: "custom", customType: "left" }),
storedValues.setValue(storedValues.sessionName, "left-name"),
],
BACKGROUND_CONTEXT,
),
BACKGROUND_CONTEXT,
);
await right.mutate(
(mutator) =>
mutator.commit(
[
sessionWrites.insertEntry({
id: "right-root",
parentId: null,
type: "custom",
customType: "right",
}),
storedValues.setValue(storedValues.sessionName, "right-name"),
],
BACKGROUND_CONTEXT,
),
BACKGROUND_CONTEXT,
);
expect((await repo.list(undefined, BACKGROUND_CONTEXT)).map((metadata) => metadata.id).sort()).toEqual([
"left",
"right",
]);
expect(await left.getValue(storedValues.sessionName, BACKGROUND_CONTEXT)).toMatchObject({
value: "left-name",
});
expect(await right.getValue(storedValues.sessionName, BACKGROUND_CONTEXT)).toMatchObject({
value: "right-name",
});
expect((await left.getEntries(["left-root", "right-root"], BACKGROUND_CONTEXT)).has("right-root")).toBe(false);
expect((await right.getEntries(["left-root", "right-root"], BACKGROUND_CONTEXT)).has("left-root")).toBe(false);
await Promise.all([left.close(BACKGROUND_CONTEXT), right.close(BACKGROUND_CONTEXT)]);
});
});
it("does not copy usage ledger rows when forking", async () => {
await withTempDir(async (directory) => {
const repo = new SqliteSessionRepo({
directory,
databaseFactory: createNodeSqliteFactory(),
now: () => 1_700_000_000_000,
});
const source = await repo.create({ id: "source" }, BACKGROUND_CONTEXT);
await source.mutate(
(mutator) =>
mutator.commit(
[
sessionWrites.insertEntry({ id: "root", parentId: null, type: "custom", customType: "root" }),
sessionWrites.insertEntry({
id: "child",
parentId: "root",
type: "message",
message: { role: "user", content: "child", timestamp: 1 },
}),
storedValues.setValue(storedValues.branchTip("main"), "child"),
storedValues.setValue(storedValues.laneConfig("main"), TEST_LANE_CONFIGURATION),
storedValues.setValue(storedValues.laneState("main"), IDLE_LANE_STATE),
sessionWrites.insertUsage({
id: "usage",
adjustment: false,
usage: {
input: 1,
output: 1,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 2,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
},
}),
],
BACKGROUND_CONTEXT,
),
BACKGROUND_CONTEXT,
);
await source.close(BACKGROUND_CONTEXT);
const fork = await repo.fork(
source.metadata,
{ id: "fork", scope: "branch", branch: "main", entryId: "child" },
BACKGROUND_CONTEXT,
);
await withDb(fork.metadata.path, (db) => {
expect(
sql`SELECT COUNT(*) AS count FROM usage_ledger WHERE session_id = ${"fork"}`.get<{ count: number }>(db),
).toEqual({ count: 0 });
});
await Promise.all([source.close(BACKGROUND_CONTEXT), fork.close(BACKGROUND_CONTEXT)]);
});
});
it("does not create databases for missing open, list, fork, or delete targets", async () => {
await withTempDir(async (root) => {
const directory = join(root, "missing-directory");
const repo = new SqliteSessionRepo({
directory,
databaseFactory: createNodeSqliteFactory(),
now: () => 1,
});
const missing = {
id: "missing",
createdAt: 1,
storageVersion: 1,
path: join(directory, "missing.sqlite"),
};
await expect(repo.list(undefined, BACKGROUND_CONTEXT)).resolves.toEqual([]);
expect(await pathExists(directory)).toBe(false);
await expect(repo.open(missing, BACKGROUND_CONTEXT)).rejects.toThrow();
await expect(repo.delete(missing, BACKGROUND_CONTEXT)).rejects.toThrow();
await expect(repo.fork(missing, { id: "fork", scope: "tree" }, BACKGROUND_CONTEXT)).rejects.toThrow();
expect(await pathExists(missing.path)).toBe(false);
expect(await pathExists(join(directory, "fork.sqlite"))).toBe(false);
});
});
it("reserves deletion against local create, open, and fork destinations", async () => {
await withTempDir(async (directory) => {
const databaseFactory = new GatedOpenExistingFactory();
const repo = new SqliteSessionRepo({ directory, databaseFactory, now: () => 1 });
const target = await repo.create({ id: "target" }, BACKGROUND_CONTEXT);
const source = await repo.create({ id: "source" }, BACKGROUND_CONTEXT);
await expect(repo.delete(target.metadata, BACKGROUND_CONTEXT)).rejects.toThrow("already open");
// The host closes a worker before deletion; only same-repository exclusion is promised here.
await target.close(BACKGROUND_CONTEXT);
databaseFactory.arm();
const deleting = repo.delete(target.metadata, BACKGROUND_CONTEXT);
await databaseFactory.entered.promise;
await expect(repo.create({ id: "target" }, BACKGROUND_CONTEXT)).rejects.toThrow("already open");
await expect(repo.open(target.metadata, BACKGROUND_CONTEXT)).rejects.toThrow("already open");
await expect(repo.fork(source.metadata, { id: "target", scope: "tree" }, BACKGROUND_CONTEXT)).rejects.toThrow(
"already open",
);
databaseFactory.release.resolve();
await deleting;
await source.close(BACKGROUND_CONTEXT);
});
});
it("deletes only the selected Session from a shared container", async () => {
await withTempDir(async (directory) => {
const databasePath = join(directory, "shared.sqlite");
const repo = new SqliteSessionRepo({
directory,
databasePath,
databaseFactory: createNodeSqliteFactory(),
now: () => 1,
});
const removed = await repo.create({ id: "removed" }, BACKGROUND_CONTEXT);
const retained = await repo.create({ id: "retained" }, BACKGROUND_CONTEXT);
await removed.setName("removed", BACKGROUND_CONTEXT);
await retained.setName("retained", BACKGROUND_CONTEXT);
await removed.close(BACKGROUND_CONTEXT);
await repo.delete(removed.metadata, BACKGROUND_CONTEXT);
expect((await repo.list(undefined, BACKGROUND_CONTEXT)).map(({ id }) => id)).toEqual(["retained"]);
expect(await retained.getName(BACKGROUND_CONTEXT)).toBe("retained");
await expect(repo.open(removed.metadata, BACKGROUND_CONTEXT)).rejects.toThrow("Unknown SQLite session");
await retained.close(BACKGROUND_CONTEXT);
});
});
it("removes per-file WAL and SHM sidecars", async () => {
await withTempDir(async (directory) => {
const repo = new SqliteSessionRepo({
directory,
databaseFactory: createNodeSqliteFactory(),
now: () => 1,
});
const session = await repo.create({ id: "session" }, BACKGROUND_CONTEXT);
await session.close(BACKGROUND_CONTEXT);
await writeFile(`${session.metadata.path}-wal`, "");
await writeFile(`${session.metadata.path}-shm`, "");
await repo.delete(session.metadata, BACKGROUND_CONTEXT);
await expect(access(session.metadata.path)).rejects.toThrow();
await expect(access(`${session.metadata.path}-wal`)).rejects.toThrow();
await expect(access(`${session.metadata.path}-shm`)).rejects.toThrow();
});
});
it("creates the parent of a custom shared-container path", async () => {
await withTempDir(async (directory) => {
const databasePath = join(directory, "nested", "containers", "sessions.sqlite");
const repo = new SqliteSessionRepo({
directory: join(directory, "unrelated"),
databasePath,
databaseFactory: createNodeSqliteFactory(),
now: () => 1,
});
const session = await repo.create({ id: "session" }, BACKGROUND_CONTEXT);
expect(session.metadata.path).toBe(await realpath(databasePath));
await session.close(BACKGROUND_CONTEXT);
});
});
it("encodes unsafe explicit IDs inside the repository directory and round-trips them", async () => {
await withTempDir(async (directory) => {
const repo = new SqliteSessionRepo({
directory,
databaseFactory: createNodeSqliteFactory(),
now: () => 1,
});
const ids = ["../escape", "slash/id", "back\\slash", "percent%id", "..", "dots...id", "ユニコード"];
const metadata = [];
for (const id of ids) {
const session = await repo.create({ id }, BACKGROUND_CONTEXT);
metadata.push(session.metadata);
await session.close(BACKGROUND_CONTEXT);
}
const canonicalDirectory = await realpath(directory);
for (const stored of metadata) {
const fromDirectory = relative(canonicalDirectory, stored.path);
expect(isAbsolute(fromDirectory)).toBe(false);
expect(fromDirectory.startsWith(".."), stored.id).toBe(false);
expect(dirname(stored.path)).toBe(canonicalDirectory);
}
expect((await repo.list(undefined, BACKGROUND_CONTEXT)).map(({ id }) => id).sort()).toEqual([...ids].sort());
for (const stored of metadata) {
const reopened = await repo.open(stored, BACKGROUND_CONTEXT);
expect(reopened.metadata.id).toBe(stored.id);
await reopened.close(BACKGROUND_CONTEXT);
}
const fork = await repo.fork(metadata[0]!, { id: "fork/../%", scope: "tree" }, BACKGROUND_CONTEXT);
expect(fork.metadata).toMatchObject({ id: "fork/../%", parentSessionId: "../escape" });
expect(dirname(fork.metadata.path)).toBe(canonicalDirectory);
await fork.close(BACKGROUND_CONTEXT);
});
});
it("uses exact physical identity for foreign fork sources and rejects foreign writable metadata", async () => {
await withTempDir(async (root) => {
const leftRepo = new SqliteSessionRepo({
directory: join(root, "left"),
databaseFactory: createNodeSqliteFactory(),
now: () => 1,
});
const rightRepo = new SqliteSessionRepo({
directory: join(root, "right"),
databaseFactory: createNodeSqliteFactory(),
now: () => 1,
});
const left = await leftRepo.create({ id: "same" }, BACKGROUND_CONTEXT);
const right = await rightRepo.create({ id: "same" }, BACKGROUND_CONTEXT);
await left.mutate(
(mutator) =>
mutator.commit(
[
sessionWrites.insertEntry({ id: "left-root", parentId: null, type: "custom", customType: "left" }),
storedValues.setValue(storedValues.branchTip("main"), "left-root"),
storedValues.setValue(storedValues.laneConfig("main"), TEST_LANE_CONFIGURATION),
storedValues.setValue(storedValues.laneState("main"), IDLE_LANE_STATE),
storedValues.setValue(storedValues.sessionName, "left"),
],
BACKGROUND_CONTEXT,
),
BACKGROUND_CONTEXT,
);
await right.mutate(
(mutator) =>
mutator.commit(
[
sessionWrites.insertEntry({
id: "right-root",
parentId: null,
type: "custom",
customType: "right",
}),
storedValues.setValue(storedValues.branchTip("main"), "right-root"),
storedValues.setValue(storedValues.sessionName, "right"),
],
BACKGROUND_CONTEXT,
),
BACKGROUND_CONTEXT,
);
const fork = await rightRepo.fork(
left.metadata,
{ id: "fork-left", scope: "branch", branch: "main" },
BACKGROUND_CONTEXT,
);
expect((await fork.findEntries({ order: "asc" }, BACKGROUND_CONTEXT)).map(({ id }) => id)).toEqual([
"left-root",
]);
expect(await fork.getName(BACKGROUND_CONTEXT)).toBe("left");
await right.close(BACKGROUND_CONTEXT);
await expect(rightRepo.open(left.metadata, BACKGROUND_CONTEXT)).rejects.toThrow("outside this repository");
await expect(rightRepo.delete(left.metadata, BACKGROUND_CONTEXT)).rejects.toThrow("outside this repository");
await Promise.all([left.close(BACKGROUND_CONTEXT), fork.close(BACKGROUND_CONTEXT)]);
});
});
for (const layout of ["per-file", "shared"] as const) {
it(`forks a live ${layout} source through one coherent read-only WAL snapshot`, async () => {
await withTempDir(async (directory) => {
const databasePath = layout === "shared" ? join(directory, "shared.sqlite") : undefined;
const workerRepo = new SqliteSessionRepo({
directory,
...(databasePath === undefined ? {} : { databasePath }),
databaseFactory: createNodeSqliteFactory(),
now: () => 1,
});
const source = await workerRepo.create({ id: "source" }, BACKGROUND_CONTEXT);
await source.mutate(
(mutator) =>
mutator.commit(
[
sessionWrites.insertEntry({ id: "root", parentId: null, type: "custom", customType: "root" }),
storedValues.setValue(storedValues.branchTip("main"), "root"),
storedValues.setValue(storedValues.laneConfig("main"), TEST_LANE_CONFIGURATION),
storedValues.setValue(storedValues.laneState("main"), IDLE_LANE_STATE),
storedValues.setValue(storedValues.sessionName, "before"),
storedValues.setValue(storedValues.entryLabel("root"), "before-label"),
],
BACKGROUND_CONTEXT,
),
BACKGROUND_CONTEXT,
);
const writerDb = await createNodeSqliteFactory().openExisting(source.metadata.path);
writerDb.exec("PRAGMA busy_timeout = 5000;");
let laterCommitCompleted = false;
const databaseFactory = new SnapshotBoundaryFactory(() => {
if (laterCommitCompleted) return;
commitLaterSourceState(writerDb);
laterCommitCompleted = true;
});
const serverRepo = new SqliteSessionRepo({
directory,
...(databasePath === undefined ? {} : { databasePath }),
databaseFactory,
now: () => 2,
});
const first = await serverRepo.fork(
source.metadata,
{ id: "first-fork", scope: "branch", branch: "main" },
BACKGROUND_CONTEXT,
);
expect(laterCommitCompleted).toBe(true);
expect(databaseFactory.readOnlyOpenCount).toBe(1);
expect((await first.findEntries({ order: "asc" }, BACKGROUND_CONTEXT)).map(({ id }) => id)).toEqual([
"root",
]);
expect(await first.getName(BACKGROUND_CONTEXT)).toBe("before");
expect(await first.getLabel("root", BACKGROUND_CONTEXT)).toBe("before-label");
await expect((await first.branch("main", BACKGROUND_CONTEXT))?.getTipId(BACKGROUND_CONTEXT)).resolves.toBe(
"root",
);
expect((await first.getStats(BACKGROUND_CONTEXT)).messageCount).toBe(0);
const second = await serverRepo.fork(
source.metadata,
{ id: "second-fork", scope: "branch", branch: "main" },
BACKGROUND_CONTEXT,
);
expect(databaseFactory.readOnlyOpenCount).toBe(2);
expect((await second.findEntries({ order: "asc" }, BACKGROUND_CONTEXT)).map(({ id }) => id)).toEqual([
"root",
"child",
]);
expect(await second.getName(BACKGROUND_CONTEXT)).toBe("after");
expect(await second.getLabel("root", BACKGROUND_CONTEXT)).toBe("after-label");
await expect((await second.branch("main", BACKGROUND_CONTEXT))?.getTipId(BACKGROUND_CONTEXT)).resolves.toBe(
"child",
);
expect((await second.getStats(BACKGROUND_CONTEXT)).messageCount).toBe(1);
writerDb.close();
await Promise.all([
source.close(BACKGROUND_CONTEXT),
first.close(BACKGROUND_CONTEXT),
second.close(BACKGROUND_CONTEXT),
]);
});
});
}
it("waits for every Session close and reports one or multiple failures after settlement", async () => {
await withTempDir(async (directory) => {
const multipleFactory = new CloseTrackingFactory();
const multipleRepo = new SqliteSessionRepo({
directory: join(directory, "multiple"),
databaseFactory: multipleFactory,
now: () => 1,
});
await multipleRepo.create({ id: "first" }, BACKGROUND_CONTEXT);
await multipleRepo.create({ id: "second" }, BACKGROUND_CONTEXT);
await multipleRepo.create({ id: "third" }, BACKGROUND_CONTEXT);
const firstError = new Error("first close failed");
const secondError = new Error("second close failed");
multipleFactory.writableConnections[0]!.closeError = firstError;
multipleFactory.writableConnections[1]!.closeError = secondError;
const multipleClose = multipleRepo.close(BACKGROUND_CONTEXT);
expect(multipleRepo.close(BACKGROUND_CONTEXT)).toBe(multipleClose);
let multipleFailure: unknown;
try {
await multipleClose;
} catch (error) {
multipleFailure = error;
}
expect(multipleFailure).toBeInstanceOf(AggregateError);
if (!(multipleFailure instanceof AggregateError)) throw new Error("Expected AggregateError");
expect(multipleFailure.errors).toEqual([firstError, secondError]);
expect(multipleFactory.writableConnections.map(({ closeAttempts }) => closeAttempts)).toEqual([1, 1, 1]);
const singleFactory = new CloseTrackingFactory();
const singleRepo = new SqliteSessionRepo({
directory: join(directory, "single"),
databaseFactory: singleFactory,
now: () => 1,
});
await singleRepo.create({ id: "first" }, BACKGROUND_CONTEXT);
await singleRepo.create({ id: "second" }, BACKGROUND_CONTEXT);
const singleError = new Error("single close failed");
singleFactory.writableConnections[0]!.closeError = singleError;
await expect(singleRepo.close(BACKGROUND_CONTEXT)).rejects.toBe(singleError);
expect(singleFactory.writableConnections.map(({ closeAttempts }) => closeAttempts)).toEqual([1, 1]);
});
});
});

View File

@@ -0,0 +1,39 @@
import { describe, expect, it } from "vitest";
import { createNodeSqliteFactory, joinSqlFragments, sql } from "../src/index.ts";
describe("sql", () => {
it("composes SQLite queries without renumbering parameters", async () => {
const db = await createNodeSqliteFactory().open(":memory:");
try {
sql`CREATE TABLE entries (id TEXT PRIMARY KEY, kind TEXT NOT NULL, active INTEGER NOT NULL)`.exec(db);
sql`INSERT INTO entries (id, kind, active) VALUES (${"one"}, ${"message"}, ${1})`.run(db);
sql`INSERT INTO entries (id, kind, active) VALUES (${"two"}, ${"message"}, ${0})`.run(db);
const filters = joinSqlFragments([sql`kind = ${"message"}`, sql`active = ${1}`], " AND ");
expect(sql`SELECT id FROM entries WHERE ${filters} LIMIT ${10}`.all<{ id: string }>(db)).toEqual([
{ id: "one" },
]);
} finally {
db.close();
}
});
it("executes parameterized queries", async () => {
const db = await createNodeSqliteFactory().open(":memory:");
try {
sql`CREATE TABLE values_table (id INTEGER PRIMARY KEY, value TEXT NOT NULL)`.exec(db);
sql`INSERT INTO values_table (id, value) VALUES (${1}, ${"one"})`.run(db);
sql`INSERT INTO values_table (id, value) VALUES (${2}, ${"two"})`.run(db);
expect(sql`SELECT value FROM values_table WHERE id = ${1}`.get<{ value: string }>(db)).toEqual({
value: "one",
});
expect(sql`SELECT value FROM values_table ORDER BY id`.all<{ value: string }>(db)).toEqual([
{ value: "one" },
{ value: "two" },
]);
} finally {
db.close();
}
});
});

View File

@@ -0,0 +1,61 @@
import { BACKGROUND_CONTEXT } from "@earendil-works/pi-agent-core";
import {
type ConformanceCase,
createStorageConformance,
type StorageFixture,
} from "@earendil-works/pi-agent-core/harness/session/testing";
import { describe, it } from "vitest";
import { createNodeSqliteFactory, SQLITE_STORAGE_VERSION, SqliteStorage, sql } from "../src/index.ts";
import { applyInitialSchema } from "../src/sqlite/migrations.ts";
const SESSION_ID = "session";
const NOW = 1_700_000_000_000;
const EMPTY_USAGE = {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
};
function registerConformance(name: string, cases: readonly ConformanceCase[]): void {
describe(name, () => {
for (const group of new Set(cases.map((testCase) => testCase.group))) {
describe(group, () => {
for (const testCase of cases.filter((candidate) => candidate.group === group)) {
it(testCase.name, () => testCase.run());
}
});
}
});
}
registerConformance(
"SqliteStorage conformance",
createStorageConformance(async () => {
const db = await createNodeSqliteFactory().open(":memory:");
try {
await applyInitialSchema(db);
sql`INSERT INTO sessions
(id, created_at, parent_session_id, storage_version, metadata, message_count, usage_payload, next_seq)
VALUES (${SESSION_ID}, ${NOW}, ${null}, ${SQLITE_STORAGE_VERSION}, ${null}, ${0}, ${JSON.stringify(EMPTY_USAGE)}, ${1})`.run(
db,
);
const storage = new SqliteStorage(db, { sessionId: SESSION_ID, now: () => NOW });
return {
storage,
async [Symbol.asyncDispose]() {
try {
await storage.close(BACKGROUND_CONTEXT);
} finally {
db.close();
}
},
} satisfies StorageFixture;
} catch (error) {
db.close();
throw error;
}
}),
);

View File

@@ -0,0 +1,696 @@
import * as storedValues from "@earendil-works/pi-agent-core";
import * as sessionWrites from "@earendil-works/pi-agent-core";
import { BACKGROUND_CONTEXT, prepareStorageCommit } from "@earendil-works/pi-agent-core";
import { describe, expect, it } from "vitest";
import {
createNodeSqliteFactory,
type SqliteDatabase,
type SqliteStatement,
SqliteStorage,
sql,
} from "../src/index.ts";
import { applyInitialSchema } from "../src/sqlite/migrations.ts";
import { advanceNextSeq, readNextSeq } from "../src/sqlite/session/session-sequences.ts";
import { listValueReadQuery } from "../src/sqlite/session/values.ts";
const SESSION_ID = "session";
class TransactionCountingDatabase implements SqliteDatabase {
readonly source: SqliteDatabase;
transactionCount = 0;
constructor(source: SqliteDatabase) {
this.source = source;
}
exec(query: string): void {
this.source.exec(query);
}
prepare(query: string): SqliteStatement {
return this.source.prepare(query);
}
transaction<T>(callback: () => T): T {
this.transactionCount++;
return this.source.transaction(callback);
}
close(): void {
this.source.close();
}
}
const ZERO_USAGE = {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
};
async function withStorage<T>(run: (storage: SqliteStorage, db: SqliteDatabase) => Promise<T>): Promise<T> {
const db = await createNodeSqliteFactory().open(":memory:");
try {
await applyInitialSchema(db);
const storage = new SqliteStorage(db, { sessionId: SESSION_ID, now: () => 1_700_000_000_000 });
return await run(storage, db);
} finally {
db.close();
}
}
function explainQueryPlan(db: SqliteDatabase, query: string, ...params: unknown[]): string[] {
return db
.prepare(`EXPLAIN QUERY PLAN ${query}`)
.all<{ detail: string }>(...params)
.map((row) => row.detail);
}
function expectBranchPlan(plan: string[]): void {
expect(plan.some((detail) => detail.includes("SEARCH b USING COVERING INDEX ix_be_seq"))).toBe(true);
expect(plan.some((detail) => detail.includes("SEARCH e USING PRIMARY KEY"))).toBe(true);
expect(plan.some((detail) => detail.includes("USE TEMP B-TREE"))).toBe(false);
expect(plan.some((detail) => detail.includes("SCAN e"))).toBe(false);
}
function insertCommitSessionRow(db: SqliteDatabase, nextSeq = 1): void {
sql`INSERT INTO sessions
(id, created_at, parent_session_id, storage_version, metadata, message_count, usage_payload, next_seq)
VALUES (
${SESSION_ID},
${1},
${null},
${1},
${null},
${0},
${JSON.stringify(ZERO_USAGE)},
${nextSeq}
)`.run(db);
}
describe("SqliteStorage", () => {
it("uses one write transaction for an ordinary commit", async () => {
const db = new TransactionCountingDatabase(await createNodeSqliteFactory().open(":memory:"));
try {
await applyInitialSchema(db);
insertCommitSessionRow(db);
const storage = new SqliteStorage(db, { sessionId: SESSION_ID, now: () => 1_700_000_000_000 });
await storage.commit([storedValues.setValue(storedValues.sessionName, "name")], BACKGROUND_CONTEXT);
expect(db.transactionCount).toBe(1);
await storage.close(BACKGROUND_CONTEXT);
} finally {
db.close();
}
});
it("commits root entries and append-to-tip entries into the branch index", async () => {
await withStorage(async (storage, db) => {
insertCommitSessionRow(db);
expect(
await storage.commit(
[
sessionWrites.insertEntry({
id: "root",
parentId: null,
type: "message",
message: { role: "user", content: "root", timestamp: 10 },
}),
],
BACKGROUND_CONTEXT,
),
).toEqual({
firstSeq: 1,
seqs: [1],
timestamp: 1_700_000_000_000,
stats: { messageCount: 1, usage: ZERO_USAGE },
});
expect(
await storage.commit(
[
sessionWrites.insertEntry({
id: "child",
parentId: "root",
type: "message",
message: { role: "user", content: "child", timestamp: 11 },
}),
],
BACKGROUND_CONTEXT,
),
).toEqual({
firstSeq: 2,
seqs: [2],
timestamp: 1_700_000_000_000,
stats: { messageCount: 2, usage: ZERO_USAGE },
});
expect(sql`SELECT branch_id, tip_entry_id, tip_seq FROM branch_meta`.all(db)).toEqual([
{ branch_id: "root", tip_entry_id: "child", tip_seq: 2 },
]);
expect(
sql`SELECT branch_id, entry_id, entry_seq, entry_type FROM branch_entries ORDER BY entry_seq`.all(db),
).toEqual([
{ branch_id: "root", entry_id: "root", entry_seq: 1, entry_type: "message" },
{ branch_id: "root", entry_id: "child", entry_seq: 2, entry_type: "message" },
]);
expect((await storage.scanBranch({ start: "child" }, BACKGROUND_CONTEXT)).map((entry) => entry.id)).toEqual([
"child",
"root",
]);
});
});
it("commits divergent branch entries by materializing a new segment", async () => {
await withStorage(async (storage, db) => {
insertCommitSessionRow(db);
await storage.commit(
[
sessionWrites.insertEntry({
id: "root",
parentId: null,
type: "message",
message: { role: "user", content: "root", timestamp: 10 },
}),
sessionWrites.insertEntry({
id: "left",
parentId: "root",
type: "message",
message: { role: "user", content: "left", timestamp: 11 },
}),
sessionWrites.insertEntry({
id: "right",
parentId: "root",
type: "message",
message: { role: "user", content: "right", timestamp: 12 },
}),
],
BACKGROUND_CONTEXT,
);
expect(
sql`SELECT branch_id, tip_entry_id, tip_seq, base_branch_id, base_seq FROM branch_meta ORDER BY branch_id`.all(
db,
),
).toEqual([
{ branch_id: "right", tip_entry_id: "right", tip_seq: 3, base_branch_id: null, base_seq: null },
{ branch_id: "root", tip_entry_id: "left", tip_seq: 2, base_branch_id: null, base_seq: null },
]);
expect(
sql`SELECT branch_id, entry_id, entry_seq, entry_type FROM branch_entries ORDER BY branch_id, entry_seq`.all(
db,
),
).toEqual([
{ branch_id: "right", entry_id: "root", entry_seq: 1, entry_type: "message" },
{ branch_id: "right", entry_id: "right", entry_seq: 3, entry_type: "message" },
{ branch_id: "root", entry_id: "root", entry_seq: 1, entry_type: "message" },
{ branch_id: "root", entry_id: "left", entry_seq: 2, entry_type: "message" },
]);
expect((await storage.scanBranch({ start: "right" }, BACKGROUND_CONTEXT)).map((entry) => entry.id)).toEqual([
"right",
"root",
]);
expect((await storage.scanBranch({ start: "left" }, BACKGROUND_CONTEXT)).map((entry) => entry.id)).toEqual([
"left",
"root",
]);
});
});
it("bases divergent branch segments at the newest compaction", async () => {
await withStorage(async (storage, db) => {
insertCommitSessionRow(db);
await storage.commit(
[
sessionWrites.insertEntry({
id: "root",
parentId: null,
type: "message",
message: { role: "user", content: "root", timestamp: 10 },
}),
sessionWrites.insertEntry({
id: "compact",
parentId: "root",
type: "compaction",
summary: "summary",
retainedTail: [],
tokensBefore: 1,
fromHook: false,
}),
sessionWrites.insertEntry({
id: "left",
parentId: "compact",
type: "message",
message: { role: "user", content: "left", timestamp: 11 },
}),
sessionWrites.insertEntry({
id: "leaf",
parentId: "left",
type: "message",
message: { role: "user", content: "leaf", timestamp: 12 },
}),
sessionWrites.insertEntry({
id: "right",
parentId: "left",
type: "message",
message: { role: "user", content: "right", timestamp: 13 },
}),
],
BACKGROUND_CONTEXT,
);
expect(
sql`SELECT branch_id, tip_entry_id, tip_seq, base_branch_id, base_seq FROM branch_meta WHERE branch_id = ${"right"}`.get(
db,
),
).toEqual({
branch_id: "right",
tip_entry_id: "right",
tip_seq: 5,
base_branch_id: "root",
base_seq: 2,
});
expect(
sql`SELECT branch_id, entry_id, entry_seq, entry_type FROM branch_entries WHERE branch_id = ${"right"} ORDER BY entry_seq`.all(
db,
),
).toEqual([
{ branch_id: "right", entry_id: "left", entry_seq: 3, entry_type: "message" },
{ branch_id: "right", entry_id: "right", entry_seq: 5, entry_type: "message" },
]);
expect((await storage.scanBranch({ start: "right" }, BACKGROUND_CONTEXT)).map((entry) => entry.id)).toEqual([
"right",
"left",
"compact",
"root",
]);
});
});
it("gets entries by requested id order", async () => {
await withStorage(async (storage, db) => {
sql`INSERT INTO entries (session_id, id, parent_id, seq, type, custom_type, timestamp, payload)
VALUES
(${SESSION_ID}, ${"first"}, ${null}, ${1}, ${"custom"}, ${"note"}, ${10}, ${JSON.stringify({ data: { value: 1 } })}),
(${SESSION_ID}, ${"second"}, ${"first"}, ${2}, ${"message"}, ${null}, ${11}, ${JSON.stringify({ message: { role: "user", content: "hi", timestamp: 11 } })})`.run(
db,
);
const entries = await storage.getEntries(["second", "missing", "first"], BACKGROUND_CONTEXT);
expect([...entries.keys()]).toEqual(["second", "first"]);
expect(entries.get("second")).toMatchObject({ id: "second", type: "message", parentId: "first" });
expect(entries.get("first")).toMatchObject({ id: "first", type: "custom", customType: "note" });
});
});
it("scans decoded entries with filters and sequence bounds", async () => {
await withStorage(async (storage, db) => {
sql`INSERT INTO entries (session_id, id, parent_id, seq, type, custom_type, timestamp, payload)
VALUES
(${SESSION_ID}, ${"one"}, ${null}, ${1}, ${"custom"}, ${"note"}, ${10}, ${JSON.stringify({ data: 1 })}),
(${SESSION_ID}, ${"two"}, ${"one"}, ${2}, ${"message"}, ${null}, ${11}, ${JSON.stringify({ message: { role: "user", content: "two", timestamp: 11 } })}),
(${SESSION_ID}, ${"three"}, ${"two"}, ${3}, ${"custom"}, ${"note"}, ${12}, ${JSON.stringify({ data: 3 })})`.run(
db,
);
expect(
(await storage.scanEntries({ order: "desc", type: "custom", fromSeq: 2 }, BACKGROUND_CONTEXT)).map(
(entry) => entry.id,
),
).toEqual(["three"]);
expect(
(await storage.scanEntries({ order: "asc", customType: "note", limit: 2 }, BACKGROUND_CONTEXT)).map(
(entry) => entry.id,
),
).toEqual(["one", "three"]);
});
});
it("scans branch entries through materialized branch segments", async () => {
await withStorage(async (storage, db) => {
sql`INSERT INTO entries (session_id, id, parent_id, seq, type, custom_type, timestamp, payload)
VALUES
(${SESSION_ID}, ${"root"}, ${null}, ${1}, ${"message"}, ${null}, ${10}, ${JSON.stringify({ message: { role: "user", content: "root", timestamp: 10 } })}),
(${SESSION_ID}, ${"compact"}, ${"root"}, ${2}, ${"compaction"}, ${null}, ${11}, ${JSON.stringify({ summary: "s", retainedTail: [], tokensBefore: 1, fromHook: false })}),
(${SESSION_ID}, ${"old"}, ${"compact"}, ${3}, ${"message"}, ${null}, ${12}, ${JSON.stringify({ message: { role: "assistant", content: "old", timestamp: 12 } })}),
(${SESSION_ID}, ${"custom"}, ${"old"}, ${4}, ${"custom"}, ${"note"}, ${13}, ${JSON.stringify({ data: 4 })}),
(${SESSION_ID}, ${"leaf"}, ${"custom"}, ${5}, ${"message"}, ${null}, ${14}, ${JSON.stringify({ message: { role: "user", content: "leaf", timestamp: 14 } })})`.run(
db,
);
sql`INSERT INTO branch_meta (session_id, branch_id, tip_entry_id, tip_seq, base_branch_id, base_seq)
VALUES
(${SESSION_ID}, ${"base"}, ${"compact"}, ${2}, ${null}, ${null}),
(${SESSION_ID}, ${"new"}, ${"leaf"}, ${5}, ${"base"}, ${2})`.run(db);
sql`INSERT INTO branch_entries (session_id, branch_id, entry_id, entry_seq, entry_type)
VALUES
(${SESSION_ID}, ${"base"}, ${"root"}, ${1}, ${"message"}),
(${SESSION_ID}, ${"base"}, ${"compact"}, ${2}, ${"compaction"}),
(${SESSION_ID}, ${"new"}, ${"old"}, ${3}, ${"message"}),
(${SESSION_ID}, ${"new"}, ${"custom"}, ${4}, ${"custom"}),
(${SESSION_ID}, ${"new"}, ${"leaf"}, ${5}, ${"message"})`.run(db);
expect(
(await storage.scanBranch({ start: "leaf", stopAtType: "compaction", limit: 2 }, BACKGROUND_CONTEXT)).map(
(entry) => entry.id,
),
).toEqual(["leaf", "custom"]);
expect(
(
await storage.scanBranch(
{ start: "leaf", order: "oldestFirst", type: "message", cursor: { seq: 1 } },
BACKGROUND_CONTEXT,
)
).map((entry) => entry.id),
).toEqual(["old", "leaf"]);
});
});
it("resolves a branch segment that physically contains the start entry", async () => {
await withStorage(async (storage, db) => {
sql`INSERT INTO entries (session_id, id, parent_id, seq, type, custom_type, timestamp, payload)
VALUES
(${SESSION_ID}, ${"root"}, ${null}, ${1}, ${"message"}, ${null}, ${10}, ${JSON.stringify({ message: { role: "user", content: "root", timestamp: 10 } })}),
(${SESSION_ID}, ${"base-tip"}, ${"root"}, ${2}, ${"message"}, ${null}, ${11}, ${JSON.stringify({ message: { role: "assistant", content: "base", timestamp: 11 } })}),
(${SESSION_ID}, ${"new-tip"}, ${"base-tip"}, ${3}, ${"message"}, ${null}, ${12}, ${JSON.stringify({ message: { role: "user", content: "new", timestamp: 12 } })})`.run(
db,
);
sql`INSERT INTO branch_meta (session_id, branch_id, tip_entry_id, tip_seq, base_branch_id, base_seq)
VALUES
(${SESSION_ID}, ${"aaa-new"}, ${"new-tip"}, ${3}, ${"zzz-base"}, ${2}),
(${SESSION_ID}, ${"zzz-base"}, ${"base-tip"}, ${2}, ${null}, ${null})`.run(db);
sql`INSERT INTO branch_entries (session_id, branch_id, entry_id, entry_seq, entry_type)
VALUES
(${SESSION_ID}, ${"aaa-new"}, ${"new-tip"}, ${3}, ${"message"}),
(${SESSION_ID}, ${"zzz-base"}, ${"root"}, ${1}, ${"message"}),
(${SESSION_ID}, ${"zzz-base"}, ${"base-tip"}, ${2}, ${"message"})`.run(db);
expect(
(await storage.scanBranch({ start: "base-tip", order: "oldestFirst" }, BACKGROUND_CONTEXT)).map(
(entry) => entry.id,
),
).toEqual(["root", "base-tip"]);
});
});
it("applies branch stop boundaries across base segments before filtering", async () => {
await withStorage(async (storage, db) => {
sql`INSERT INTO entries (session_id, id, parent_id, seq, type, custom_type, timestamp, payload)
VALUES
(${SESSION_ID}, ${"root"}, ${null}, ${1}, ${"message"}, ${null}, ${10}, ${JSON.stringify({ message: { role: "user", content: "root", timestamp: 10 } })}),
(${SESSION_ID}, ${"compact"}, ${"root"}, ${2}, ${"compaction"}, ${null}, ${11}, ${JSON.stringify({ summary: "s", retainedTail: [], tokensBefore: 1, fromHook: false })}),
(${SESSION_ID}, ${"after"}, ${"compact"}, ${3}, ${"message"}, ${null}, ${12}, ${JSON.stringify({ message: { role: "assistant", content: "after", timestamp: 12 } })}),
(${SESSION_ID}, ${"leaf"}, ${"after"}, ${4}, ${"custom"}, ${"note"}, ${13}, ${JSON.stringify({ data: 4 })})`.run(
db,
);
sql`INSERT INTO branch_meta (session_id, branch_id, tip_entry_id, tip_seq, base_branch_id, base_seq)
VALUES
(${SESSION_ID}, ${"base"}, ${"compact"}, ${2}, ${null}, ${null}),
(${SESSION_ID}, ${"new"}, ${"leaf"}, ${4}, ${"base"}, ${2})`.run(db);
sql`INSERT INTO branch_entries (session_id, branch_id, entry_id, entry_seq, entry_type)
VALUES
(${SESSION_ID}, ${"base"}, ${"root"}, ${1}, ${"message"}),
(${SESSION_ID}, ${"base"}, ${"compact"}, ${2}, ${"compaction"}),
(${SESSION_ID}, ${"new"}, ${"after"}, ${3}, ${"message"}),
(${SESSION_ID}, ${"new"}, ${"leaf"}, ${4}, ${"custom"})`.run(db);
expect(
(await storage.scanBranch({ start: "leaf", stopAtType: "compaction" }, BACKGROUND_CONTEXT)).map(
(entry) => entry.id,
),
).toEqual(["leaf", "after", "compact"]);
expect(
(
await storage.scanBranch(
{ start: "leaf", stopAtType: "compaction", type: "message" },
BACKGROUND_CONTEXT,
)
).map((entry) => entry.id),
).toEqual(["after"]);
});
});
it("uses branch_entries as the outer scan for branch payload queries", async () => {
await withStorage(async (_storage, db) => {
const plan = explainQueryPlan(
db,
`SELECT e.id, e.parent_id, e.seq, e.type, e.custom_type, e.timestamp, e.payload
FROM branch_entries b
CROSS JOIN entries e ON e.session_id = b.session_id AND e.id = b.entry_id
WHERE b.session_id = ? AND b.branch_id = ? AND b.entry_seq > ? AND b.entry_seq <= ?
ORDER BY b.entry_seq DESC LIMIT ?`,
SESSION_ID,
"main",
0,
10,
2,
);
expectBranchPlan(plan);
});
});
it("scans branch structure without payloads", async () => {
await withStorage(async (storage, db) => {
sql`INSERT INTO entries (session_id, id, parent_id, seq, type, custom_type, timestamp, payload)
VALUES
(${SESSION_ID}, ${"root"}, ${null}, ${1}, ${"message"}, ${null}, ${10}, ${JSON.stringify({ message: { role: "user", content: "root", timestamp: 10 } })}),
(${SESSION_ID}, ${"custom"}, ${"root"}, ${2}, ${"custom"}, ${"note"}, ${11}, ${JSON.stringify({ data: 2 })})`.run(
db,
);
sql`INSERT INTO branch_meta (session_id, branch_id, tip_entry_id, tip_seq, base_branch_id, base_seq)
VALUES (${SESSION_ID}, ${"main"}, ${"custom"}, ${2}, ${null}, ${null})`.run(db);
sql`INSERT INTO branch_entries (session_id, branch_id, entry_id, entry_seq, entry_type)
VALUES
(${SESSION_ID}, ${"main"}, ${"root"}, ${1}, ${"message"}),
(${SESSION_ID}, ${"main"}, ${"custom"}, ${2}, ${"custom"})`.run(db);
expect(await storage.scanBranchStructure({ start: "custom", customType: "note" }, BACKGROUND_CONTEXT)).toEqual(
[
{
id: "custom",
parentId: "root",
seq: 2,
timestamp: 11,
type: "custom",
customType: "note",
},
],
);
});
});
it("uses branch_entries as the outer scan for branch structure queries", async () => {
await withStorage(async (_storage, db) => {
const plan = explainQueryPlan(
db,
`SELECT e.id, e.parent_id, e.seq, e.type, e.custom_type, e.timestamp
FROM branch_entries b
CROSS JOIN entries e ON e.session_id = b.session_id AND e.id = b.entry_id
WHERE b.session_id = ? AND b.branch_id = ? AND b.entry_seq > ? AND b.entry_seq <= ?
ORDER BY b.entry_seq ASC LIMIT ?`,
SESSION_ID,
"main",
0,
10,
2,
);
expectBranchPlan(plan);
});
});
it("scans decoded usage rows with sequence bounds", async () => {
await withStorage(async (storage, db) => {
const usage = {
input: 1,
output: 2,
cacheRead: 3,
cacheWrite: 4,
totalTokens: 10,
cost: { input: 0.1, output: 0.2, cacheRead: 0.3, cacheWrite: 0.4, total: 1 },
};
sql`INSERT INTO usage_ledger (session_id, id, seq, entry_id, adjustment, usage, details)
VALUES
(${SESSION_ID}, ${"u1"}, ${1}, ${"e1"}, ${0}, ${JSON.stringify(usage)}, ${null}),
(${SESSION_ID}, ${"u2"}, ${2}, ${null}, ${1}, ${JSON.stringify(usage)}, ${JSON.stringify({ reason: "adjust" })}),
(${SESSION_ID}, ${"u3"}, ${3}, ${"e3"}, ${0}, ${JSON.stringify(usage)}, ${null})`.run(db);
expect(await storage.scanUsage({ fromSeq: 2, order: "asc", limit: 1 }, BACKGROUND_CONTEXT)).toEqual([
{ id: "u2", seq: 2, usage, adjustment: true, details: { reason: "adjust" } },
]);
expect(
(await storage.scanUsage({ toSeq: 2, order: "desc" }, BACKGROUND_CONTEXT)).map((row) => row.id),
).toEqual(["u2", "u1"]);
});
});
it("prepares committed writes with assigned sequences and timestamp", () => {
const prepared = prepareStorageCommit(
[
sessionWrites.insertEntry({
id: "entry",
parentId: null,
type: "message",
message: { role: "user", content: "hi", timestamp: 1 },
}),
sessionWrites.insertUsage({
id: "usage",
usage: {
input: 1,
output: 2,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 3,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
},
adjustment: false,
}),
storedValues.setValue(storedValues.sessionName, "name"),
storedValues.deleteValue(storedValues.entryLabel("entry")),
],
7,
1_700_000_000_000,
);
expect(prepared.result).toEqual({ firstSeq: 7, seqs: [7, 8, 9, 10], timestamp: 1_700_000_000_000 });
expect(prepared.writes).toMatchObject([
{ kind: "entry", id: "entry", seq: 7, timestamp: 1_700_000_000_000 },
{ kind: "usage", id: "usage", seq: 8 },
storedValues.setValue(storedValues.sessionName, "name"),
storedValues.deleteValue(storedValues.entryLabel("entry")),
]);
});
it("reads and advances the next commit sequence", async () => {
await withStorage(async (_storage, db) => {
sql`INSERT INTO sessions
(id, created_at, parent_session_id, storage_version, metadata, message_count, usage_payload, next_seq)
VALUES (${SESSION_ID}, ${1}, ${null}, ${1}, ${null}, ${0}, ${JSON.stringify({})}, ${7})`.run(db);
expect(readNextSeq(db, SESSION_ID)).toBe(7);
advanceNextSeq(db, SESSION_ID, 10);
expect(readNextSeq(db, SESSION_ID)).toBe(10);
});
});
it("gets maintained session stats", async () => {
await withStorage(async (storage, db) => {
const usage = {
input: 1,
output: 2,
cacheRead: 3,
cacheWrite: 4,
totalTokens: 10,
cost: { input: 0.1, output: 0.2, cacheRead: 0.3, cacheWrite: 0.4, total: 1 },
};
sql`INSERT INTO sessions
(id, created_at, parent_session_id, storage_version, metadata, message_count, usage_payload, next_seq)
VALUES (${SESSION_ID}, ${1}, ${null}, ${1}, ${null}, ${2}, ${JSON.stringify(usage)}, ${3})`.run(db);
expect(await storage.getStats(BACKGROUND_CONTEXT)).toEqual({ messageCount: 2, usage });
const next = await storage.commit(
[storedValues.setValue(storedValues.sessionName, "after-history")],
BACKGROUND_CONTEXT,
);
expect(next.stats).toEqual({ messageCount: 2, usage });
});
});
it("includes historical totals in the first commit after storage reopen", async () => {
await withStorage(async (storage, db) => {
insertCommitSessionRow(db);
const usage = {
input: 1,
output: 2,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 3,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
};
await storage.commit(
[
sessionWrites.insertEntry({
id: "history",
parentId: null,
type: "message",
message: { role: "user", content: "history", timestamp: 1 },
}),
sessionWrites.insertUsage({ id: "usage", usage, adjustment: false }),
],
BACKGROUND_CONTEXT,
);
await storage.close(BACKGROUND_CONTEXT);
const reopened = new SqliteStorage(db, { sessionId: SESSION_ID, now: () => 1_700_000_000_000 });
const result = await reopened.commit(
[storedValues.setValue(storedValues.sessionName, "reopened")],
BACKGROUND_CONTEXT,
);
expect(result.stats).toEqual({ messageCount: 1, usage });
expect(result.stats).toEqual(await reopened.getStats(BACKGROUND_CONTEXT));
await reopened.close(BACKGROUND_CONTEXT);
});
});
it("gets a decoded scalar value by bound address", async () => {
await withStorage(async (storage, db) => {
const address = storedValues.value<{ ready: boolean }>("test.value", "state");
sql`INSERT INTO scalar_values (session_id, namespace, key, seq, value)
VALUES (${SESSION_ID}, ${address.namespace}, ${address.key}, ${1}, ${JSON.stringify({ ready: true })})`.run(
db,
);
expect(await storage.getValue(address, BACKGROUND_CONTEXT)).toEqual({
address,
seq: 1,
value: { ready: true },
});
expect(
await storage.getValue(storedValues.value<unknown>("test.value", "missing"), BACKGROUND_CONTEXT),
).toBeUndefined();
});
});
it("scans decoded scalar values by namespace and key prefix", async () => {
await withStorage(async (storage, db) => {
sql`INSERT INTO scalar_values (session_id, namespace, key, seq, value)
VALUES
(${SESSION_ID}, ${"test.value"}, ${"app:one"}, ${1}, ${JSON.stringify(1)}),
(${SESSION_ID}, ${"test.value"}, ${"app:two"}, ${2}, ${JSON.stringify(2)}),
(${SESSION_ID}, ${"test.value"}, ${"app:\ufffftail"}, ${3}, ${JSON.stringify(3)}),
(${SESSION_ID}, ${"test.value"}, ${"app;other"}, ${4}, ${JSON.stringify(4)}),
(${SESSION_ID}, ${"test.value"}, ${"other"}, ${5}, ${JSON.stringify(5)}),
(${SESSION_ID}, ${"other.value"}, ${""}, ${6}, ${JSON.stringify("name")})`.run(db);
expect(
await storage.scanValues(storedValues.value<unknown>("test.value", "app:"), BACKGROUND_CONTEXT),
).toEqual([
{ address: storedValues.value<unknown>("test.value", "app:one"), seq: 1, value: 1 },
{ address: storedValues.value<unknown>("test.value", "app:two"), seq: 2, value: 2 },
{ address: storedValues.value<unknown>("test.value", "app:\ufffftail"), seq: 3, value: 3 },
]);
});
});
it("uses the list primary key for ascending and descending cursor pages", async () => {
await withStorage(async (_storage, db) => {
const address = storedValues.list<unknown>("test.list", "events");
for (const options of [
{ order: "asc" as const },
{ order: "asc" as const, cursor: { seq: 4 } },
{ order: "desc" as const },
{ order: "desc" as const, cursor: { seq: 4 } },
]) {
const query = listValueReadQuery(SESSION_ID, address, options);
const plan = explainQueryPlan(db, query.queryText, ...query.params);
expect(plan.some((detail) => detail.includes("USING PRIMARY KEY"))).toBe(true);
expect(plan.some((detail) => detail.includes("SCAN list_values"))).toBe(false);
expect(plan.some((detail) => detail.includes("USE TEMP B-TREE"))).toBe(false);
}
});
});
});