Files
stack/packages/seat/tests/compactions-ledger.test.mjs

185 lines
12 KiB
JavaScript

// Row 307 (#1814, Vikunja 598): core keeps the ledger and asks the harness module for the
// compactions. A ledger row 270 wrote reads the same count after the move, the next collection
// goes through the module's `compactions`, and core refuses an answer outside the contract before
// it writes anything.
//
// FIXTURES are what row 270's collectCompactions wrote at 1e629c892, one ledger per reason it
// writes plus a torn one: each fixture's streams, Claude projects and steps, replayed there with
// the clock at 12:00:00 and one second a step, give its ledger byte for byte
// (agents/gizmoduck/work/queue-307/capture.mjs checks that). `count` is compactionCount over it,
// and `next` is the count after one more pi collection at the next second.
import { test } from "node:test";
import assert from "node:assert/strict";
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { collectCompactions, compactionCount, LEDGER } from "../src/compactions.mjs";
import { sessionStore } from "../src/session.mjs";
import * as pi from "../../plugins/harness/pi/index.mjs";
// capture:begin
const FIXTURES = {
"known": {
"streams": {"a.jsonl": "{\"type\":\"session\",\"version\":3,\"id\":\"a\",\"timestamp\":\"2026-10-22T11:00:00.000Z\",\"cwd\":\"/w\"}\n{\"type\":\"compaction\",\"id\":\"c1\",\"parentId\":null,\"timestamp\":\"2026-10-22T11:01:00.000Z\",\"summary\":\"s\",\"firstKeptEntryId\":\"k\",\"tokensBefore\":100}\n"},
"steps": [{"collect": "pi"}],
"ledger": [
"{\"v\":1,\"kind\":\"compaction\",\"entry\":\"c1\",\"completedAt\":\"2026-10-22T11:01:00.000Z\",\"stream\":\"a.jsonl\",\"at\":\"2026-10-22T12:00:00.000Z\"}",
"{\"v\":1,\"kind\":\"cursor\",\"stream\":\"a.jsonl\",\"bytes\":233,\"tail\":\"39650b0c01a938998403d9dec3f8c224a01509ccde607711e962bf68bcbff54a\",\"at\":\"2026-10-22T12:00:00.000Z\"}",
"{\"v\":1,\"kind\":\"collected\",\"at\":\"2026-10-22T12:00:00.000Z\",\"known\":true,\"reason\":null,\"harness\":\"pi\",\"version\":\"0.85.1\"}"
],
"count": {"count": 1, "known": true, "reason": null, "at": "2026-10-22T12:00:00.000Z"},
"next": {"count": 1, "known": true, "reason": null, "at": "2026-10-22T12:00:01.000Z"}
},
"store-incomplete": {
"streams": {"a.jsonl": "{\"type\":\"session\",\"version\":3,\"id\":\"a\",\"timestamp\":\"2026-10-22T11:00:00.000Z\",\"cwd\":\"/w\"}\nnot json\n"},
"steps": [{"collect": "pi"}],
"ledger": [
"{\"v\":1,\"kind\":\"cursor\",\"stream\":\"a.jsonl\",\"bytes\":99,\"tail\":\"14607eb2c2f70f460e1a420da1b4ead7f00f2a81cc4105e9e55e6c5c828d6626\",\"at\":\"2026-10-22T12:00:00.000Z\"}",
"{\"v\":1,\"kind\":\"collected\",\"at\":\"2026-10-22T12:00:00.000Z\",\"known\":false,\"reason\":\"store-incomplete\",\"harness\":\"pi\",\"version\":\"0.85.1\"}"
],
"count": {"count": null, "known": false, "reason": "store-incomplete", "at": "2026-10-22T12:00:00.000Z"},
"next": {"count": null, "known": false, "reason": "store-incomplete", "at": "2026-10-22T12:00:01.000Z"}
},
"store-unreadable": {
"streams": {"a.jsonl": "{\"type\":\"message\",\"id\":\"m1\"}\n"},
"steps": [{"collect": "pi"}],
"ledger": [
"{\"v\":1,\"kind\":\"cursor\",\"stream\":\"a.jsonl\",\"bytes\":29,\"tail\":\"91f8cd26da181eddfdbefaef87896493bf488791937a099f5899467b04fc68df\",\"at\":\"2026-10-22T12:00:00.000Z\"}",
"{\"v\":1,\"kind\":\"collected\",\"at\":\"2026-10-22T12:00:00.000Z\",\"known\":false,\"reason\":\"store-unreadable\",\"harness\":\"pi\",\"version\":\"0.85.1\"}"
],
"count": {"count": null, "known": false, "reason": "store-unreadable", "at": "2026-10-22T12:00:00.000Z"},
"next": {"count": 0, "known": true, "reason": null, "at": "2026-10-22T12:00:01.000Z"}
},
"not-collected": {
"streams": {"a.jsonl": "{\"type\":\"session\",\"version\":3,\"id\":\"a\",\"timestamp\":\"2026-10-22T11:00:00.000Z\",\"cwd\":\"/w\"}\n{\"type\":\"compaction\",\"id\":\"c1\",\"parentId\":null,\"timestamp\":\"2026-10-22T11:01:00.000Z\",\"summary\":\"s\",\"firstKeptEntryId\":\"k\",\"tokensBefore\":100}\n"},
"steps": [{"collect": "pi", "budget": 100}],
"ledger": [
"{\"v\":1,\"kind\":\"cursor\",\"stream\":\"a.jsonl\",\"bytes\":90,\"tail\":\"2dff60ffd3bc96efa6dd8d740044255d75a1f71d82e7e99a39158d18cf211fc5\",\"at\":\"2026-10-22T12:00:00.000Z\"}",
"{\"v\":1,\"kind\":\"collected\",\"at\":\"2026-10-22T12:00:00.000Z\",\"known\":false,\"reason\":\"not-collected\",\"harness\":\"pi\",\"version\":\"0.85.1\"}"
],
"count": {"count": null, "known": false, "reason": "not-collected", "at": "2026-10-22T12:00:00.000Z"},
"next": {"count": 1, "known": true, "reason": null, "at": "2026-10-22T12:00:01.000Z"}
},
"claude-code": {
"streams": {},
"steps": [{"collect": "claude-code"}],
"ledger": [
"{\"v\":1,\"kind\":\"collected\",\"at\":\"2026-10-22T12:00:00.000Z\",\"known\":false,\"reason\":\"harness-unsupported\",\"harness\":\"claude-code\",\"version\":null}"
],
"count": {"count": null, "known": false, "reason": "harness-unsupported", "at": "2026-10-22T12:00:00.000Z"},
"next": {"count": null, "known": false, "reason": "harness-unsupported", "at": "2026-10-22T12:00:01.000Z"}
},
"claude-history": {
"streams": {"a.jsonl": "{\"type\":\"session\",\"version\":3,\"id\":\"a\",\"timestamp\":\"2026-10-22T11:00:00.000Z\",\"cwd\":\"/w\"}\n{\"type\":\"compaction\",\"id\":\"c1\",\"parentId\":null,\"timestamp\":\"2026-10-22T11:01:00.000Z\",\"summary\":\"s\",\"firstKeptEntryId\":\"k\",\"tokensBefore\":100}\n"},
"projects": {"p.jsonl": "{}\n"},
"steps": [{"collect": "pi"}],
"ledger": [
"{\"v\":1,\"kind\":\"collected\",\"at\":\"2026-10-22T12:00:00.000Z\",\"known\":false,\"reason\":\"harness-unsupported\",\"harness\":\"pi\",\"version\":\"0.85.1\"}"
],
"count": {"count": null, "known": false, "reason": "harness-unsupported", "at": "2026-10-22T12:00:00.000Z"},
"next": {"count": 1, "known": false, "reason": "harness-unsupported", "at": "2026-10-22T12:00:01.000Z"}
},
"torn": {
"streams": {"a.jsonl": "{\"type\":\"session\",\"version\":3,\"id\":\"a\",\"timestamp\":\"2026-10-22T11:00:00.000Z\",\"cwd\":\"/w\"}\n{\"type\":\"compaction\",\"id\":\"c1\",\"parentId\":null,\"timestamp\":\"2026-10-22T11:01:00.000Z\",\"summary\":\"s\",\"firstKeptEntryId\":\"k\",\"tokensBefore\":100}\n"},
"steps": [{"collect": "pi"}, {"tear": "{\"v\":1,\"kind\":\"coll"}, {"collect": "pi"}],
"ledger": [
"{\"v\":1,\"kind\":\"compaction\",\"entry\":\"c1\",\"completedAt\":\"2026-10-22T11:01:00.000Z\",\"stream\":\"a.jsonl\",\"at\":\"2026-10-22T12:00:00.000Z\"}",
"{\"v\":1,\"kind\":\"cursor\",\"stream\":\"a.jsonl\",\"bytes\":233,\"tail\":\"39650b0c01a938998403d9dec3f8c224a01509ccde607711e962bf68bcbff54a\",\"at\":\"2026-10-22T12:00:00.000Z\"}",
"{\"v\":1,\"kind\":\"collected\",\"at\":\"2026-10-22T12:00:00.000Z\",\"known\":true,\"reason\":null,\"harness\":\"pi\",\"version\":\"0.85.1\"}",
"{\"v\":1,\"kind\":\"coll",
"{\"v\":1,\"kind\":\"collected\",\"at\":\"2026-10-22T12:00:01.000Z\",\"known\":true,\"reason\":null,\"harness\":\"pi\",\"version\":\"0.85.1\"}"
],
"count": {"count": 1, "known": false, "reason": "store-incomplete", "at": "2026-10-22T12:00:01.000Z"},
"next": {"count": 1, "known": false, "reason": "store-incomplete", "at": "2026-10-22T12:00:02.000Z"}
}
};
// capture:end
function setup(t, fx = { streams: {}, ledger: null }) {
const root = mkdtempSync(join(tmpdir(), "seat-compactions-ledger-"));
t.after(() => rmSync(root, { recursive: true, force: true }));
const store = sessionStore(root, "acme", "th1", "s1");
for (const [name, text] of Object.entries(fx.streams)) writeFileSync(join(store.session, name), text);
for (const [name, text] of Object.entries(fx.projects ?? {})) writeFileSync(join(store.projects, name), text);
if (fx.ledger) writeFileSync(join(store.dir, LEDGER), fx.ledger.join("\n") + "\n", { mode: 0o600 });
const ledger = () => {
try {
return readFileSync(join(store.dir, LEDGER), "utf8");
} catch (e) {
if (e.code === "ENOENT") return null;
throw e;
}
};
return { root, store, ledger };
}
for (const [name, fx] of Object.entries(FIXTURES)) {
test(`row 307 the ${name} ledger row 270 wrote reads the same count`, (t) => {
const f = setup(t, fx);
assert.deepEqual(compactionCount(f.store.dir), fx.count);
});
test(`row 307 after the ${name} ledger, one collection through pi's module answers the next count`, (t) => {
const f = setup(t, fx);
assert.deepEqual(collectCompactions(f.store, { name: "pi", compactions: pi.compactions }, { now: () => fx.next.at }), fx.next);
});
}
const ok = { found: [], cursors: {}, known: true, reason: null, version: null };
const HEX = "0".repeat(64);
const BAD = [
[null, "not an object"],
[[], "not an object"],
[{ ...ok, found: {} }, "found isn't a list"],
[{ ...ok, found: [{ entry: "", completedAt: null, stream: "a.jsonl" }] }, "a found entry has no entry id"],
[{ ...ok, found: ["c1"] }, "a found entry has no entry id"],
[{ ...ok, found: [{ entry: "c1", completedAt: 5, stream: "a.jsonl" }] }, "found c1 has a completedAt that isn't a string or null"],
[{ ...ok, found: [{ entry: "c1", completedAt: null, stream: "" }] }, "found c1 has no stream"],
[{ ...ok, cursors: [] }, "cursors isn't an object"],
[{ ...ok, cursors: { "a.jsonl": { bytes: -1, tail: HEX } } }, 'cursor "a.jsonl" isn\'t { bytes, tail }'],
[{ ...ok, cursors: { "a.jsonl": { bytes: 1.5, tail: HEX } } }, 'cursor "a.jsonl" isn\'t { bytes, tail }'],
[{ ...ok, cursors: { "a.jsonl": { bytes: 1, tail: HEX.toUpperCase().replace(/0/g, "A") } } }, 'cursor "a.jsonl" isn\'t { bytes, tail }'],
[{ ...ok, cursors: { "a.jsonl": { bytes: 1, tail: "0".repeat(63) } } }, 'cursor "a.jsonl" isn\'t { bytes, tail }'],
[{ ...ok, reason: "lost", known: false }, 'reason "lost" isn\'t one of store-incomplete, store-unreadable, not-collected, harness-unsupported'],
[{ ...ok, reason: "not-collected" }, "known must be true exactly when reason is null"],
[{ ...ok, known: false }, "known must be true exactly when reason is null"],
[{ ...ok, known: 1 }, "known must be true exactly when reason is null"],
[{ ...ok, version: 1 }, "version isn't a string or null"],
];
test("row 307 core refuses an answer outside the contract and appends nothing", (t) => {
for (const fx of [undefined, FIXTURES.known]) {
const f = setup(t, fx);
const before = f.ledger();
for (const [answer, what] of BAD) {
const message = `harness odd compactions: bad answer: ${what}`;
assert.throws(() => collectCompactions(f.store, { name: "odd", compactions: () => answer }), { message }, JSON.stringify(answer));
assert.equal(f.ledger(), before, JSON.stringify(answer));
}
// The module's own throw reaches the caller as it is, and nothing is written either.
const boom = new Error("boom");
assert.throws(() => collectCompactions(f.store, { name: "odd", compactions: () => { throw boom; } }), (e) => e === boom);
assert.equal(f.ledger(), before);
// A good answer is written: the check refuses only what's out of contract.
assert.equal(collectCompactions(f.store, { name: "odd", compactions: () => ok }, { now: () => "2026-10-22T12:00:09.000Z" }).at, "2026-10-22T12:00:09.000Z");
}
});
test("row 307 the module gets the store's two native directories, a copy of the cursors, the budget, the cap and first", (t) => {
const seen = [];
// The module writes into the cursors it got and answers the same cursor: core's ledger copy
// doesn't have it, so core writes it.
const harness = { name: "odd", compactions: (args) => (seen.push(structuredClone(args)), args.cursors.x = { bytes: 0, tail: HEX }, { ...ok, cursors: { x: { bytes: 0, tail: HEX } } }) };
const fresh = setup(t);
collectCompactions(fresh.store, harness, { budget: 123, streams: 45 });
const known = setup(t, FIXTURES.known);
collectCompactions(known.store, harness);
const store = (f) => ({ session: f.store.session, projects: f.store.projects });
assert.deepEqual(seen, [
{ store: store(fresh), cursors: {}, budget: 123, streams: 45, first: true },
{ store: store(known), cursors: { "a.jsonl": { bytes: 233, tail: "39650b0c01a938998403d9dec3f8c224a01509ccde607711e962bf68bcbff54a" } }, budget: 64 << 20, streams: 1024, first: false },
]);
assert.equal(readFileSync(join(known.store.dir, LEDGER), "utf8").split("\n").filter((l) => l.includes('"stream":"x"')).length, 1);
});