UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

77 lines (76 loc) • 4.55 kB
import { strict as assert } from "node:assert"; import { beginContract } from "./contract-harness.js"; const SESSION_ENTRY = (id, parentId) => ({ id, parentId, type: "message", timestamp: "2026-07-30T00:00:00.000Z", message: { role: "user", content: [{ type: "text", text: `t-${id}` }], timestamp: 1785340800000 }, }); export async function sessionRepoContract(make, runAssertion) { const { run, settle } = beginContract(runAssertion); const A = "0198fa00-0000-7000-8000-00000000000a"; run("RB-360 create({id}) on an id that ALREADY has history is an idempotent REOPEN — never a wipe", async () => { const repo = make(); const sess = await repo.create({ id: A }); await sess.getStorage().appendEntry(SESSION_ENTRY("e1", null)); await sess.getStorage().appendEntry(SESSION_ENTRY("e2", "e1")); const again = await repo.create({ id: A }); const entries = await again.getStorage().getEntries(); assert.deepEqual(entries.map((e) => e.id), ["e1", "e2"]); const exported = (await repo.exportEntries?.(A)) ?? []; assert.deepEqual(exported.map((e) => e.id), ["e1", "e2"]); }); run("create({id}) fresh → open() sees appended entries (round-trip floor)", async () => { const repo = make(); const sess = await repo.create({ id: A }); await sess.getStorage().appendEntry(SESSION_ENTRY("e1", null)); const opened = await repo.open({ id: A, createdAt: "" }); assert.deepEqual((await opened.getStorage().getEntries()).map((e) => e.id), ["e1"]); }); run("open(missing) throws not_found — never a silent create", async () => { const repo = make(); await assert.rejects(repo.open({ id: "0198fa00-0000-7000-8000-0000000000ff", createdAt: "" }), /not.?found/i); }); run("delete() makes the id not_found — the history is gone, not merely hidden from open()", async () => { const repo = make(); const sess = await repo.create({ id: A }); await sess.getStorage().appendEntry(SESSION_ENTRY("e1", null)); await repo.delete({ id: A, createdAt: "" }); await assert.rejects(repo.open({ id: A, createdAt: "" }), /not.?found/i); const fresh = await repo.create({ id: A }); assert.deepEqual((await fresh.getStorage().getEntries()).map((e) => e.id), []); }); run("fork() copies the log, stamps forkedFrom, and leaves the SOURCE untouched", async () => { const repo = make(); const src = await repo.create({ id: A }); await src.getStorage().appendEntry(SESSION_ENTRY("e1", null)); await src.getStorage().appendEntry(SESSION_ENTRY("e2", "e1")); const B = "0198fa00-0000-7000-8000-00000000000b"; const forked = await repo.fork({ id: A, createdAt: "" }, { id: B }); assert.deepEqual((await forked.getStorage().getEntries()).map((e) => e.id), ["e1", "e2"]); assert.equal((await forked.getMetadata()).forkedFrom, A, "fork lineage must be stamped (a reap walks it)"); await src.getStorage().appendEntry(SESSION_ENTRY("e3", "e2")); assert.deepEqual((await (await repo.open({ id: A, createdAt: "" })).getStorage().getEntries()).map((e) => e.id), ["e1", "e2", "e3"]); assert.deepEqual((await (await repo.open({ id: B, createdAt: "" })).getStorage().getEntries()).map((e) => e.id), ["e1", "e2"]); }); run("exportEntries → importEntries round-trips the log verbatim, and the imported session REPLACES what was there", async () => { const repo = make(); if (repo.exportEntries === undefined || repo.importEntries === undefined) return; const src = await repo.create({ id: A }); await src.getStorage().appendEntry(SESSION_ENTRY("e1", null)); await src.getStorage().appendEntry(SESSION_ENTRY("e2", "e1")); const donor = await repo.exportEntries(A); assert.deepEqual(donor.map((e) => e.id), ["e1", "e2"]); const C = "0198fa00-0000-7000-8000-00000000000c"; const target = await repo.create({ id: C }); await target.getStorage().appendEntry(SESSION_ENTRY("old", null)); await repo.importEntries(C, undefined, donor); const after = await repo.exportEntries(C); assert.deepEqual(after.map((e) => e.id), ["e1", "e2"]); assert.deepEqual(after.map((e) => e.parentId), [null, "e1"]); assert.equal((await repo.exportEntries(A)).length, 2, "importing into C must not disturb the donor session"); }); await settle(); }