mirror of
https://github.com/sdwolf4103/opencode-working-memory.git
synced 2026-07-17 12:56:42 +02:00
b846b34e30
Wave 1: Storage and Journal Safety - Add frozen cache TTL (1h) and size bounds (50 sessions) - Add pending journal source-aware retention (compaction-only TTL) - Add inter-process file lock with stale recovery - Move processLatestUserMessage to first transform (after isSubAgent guard) Wave 2: Promotion Ownership and Bounded Rejection - Add pendingOwnerSessionID/pendingMessageID metadata - Add owner-aware pending journal clearing - Add explicit/manual bounded retry (max 3 attempts) - Fix session.deleted cleanup idempotency Wave 3: Normalize, Security, and Cache Hardening - Fix load-time write loop (only write on security/migration change) - Add deterministic sort tie-breaker (createdAt -> id) - Add Bearer token redaction - Add processed message cache bounds - Remove priorityWithFreshness dead code Tests: 180 pass, 0 fail
84 lines
3.1 KiB
TypeScript
84 lines
3.1 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { existsSync } from "node:fs";
|
|
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { tmpdir } from "node:os";
|
|
import { spawn } from "node:child_process";
|
|
import { updateJSON } from "../src/storage.ts";
|
|
|
|
test("updateJSON serializes concurrent increments", async () => {
|
|
const root = await mkdtemp(join(tmpdir(), "wm-storage-"));
|
|
try {
|
|
const path = join(root, "counter.json");
|
|
await Promise.all(Array.from({ length: 25 }, () =>
|
|
updateJSON(path, () => ({ count: 0 }), current => ({ count: current.count + 1 })),
|
|
));
|
|
|
|
const final = await updateJSON(path, () => ({ count: 0 }), current => current);
|
|
assert.equal(final.count, 25);
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("updateJSON does not replace corrupt JSON with fallback", async () => {
|
|
const root = await mkdtemp(join(tmpdir(), "wm-storage-corrupt-"));
|
|
try {
|
|
const path = join(root, "bad.json");
|
|
await writeFile(path, "{not json", "utf8");
|
|
|
|
await assert.rejects(
|
|
updateJSON(path, () => ({ ok: true }), current => current),
|
|
/Invalid JSON/,
|
|
);
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("updateJSON recovers stale lock files left by crashed process", async () => {
|
|
const root = await mkdtemp(join(tmpdir(), "wm-storage-stale-lock-"));
|
|
try {
|
|
const path = join(root, "locked.json");
|
|
const lockPath = `${path}.lock`;
|
|
await writeFile(lockPath, `999999\n0\n`, "utf8");
|
|
|
|
const updated = await updateJSON(path, () => ({ count: 0 }), current => ({ count: current.count + 1 }));
|
|
assert.equal(updated.count, 1);
|
|
assert.equal(existsSync(lockPath), false, "stale lock file should be removed after update");
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("updateJSON serializes writes across separate node processes", async () => {
|
|
const root = await mkdtemp(join(tmpdir(), "wm-storage-xproc-"));
|
|
try {
|
|
const path = join(root, "counter.json");
|
|
const worker = `
|
|
import { updateJSON } from ${JSON.stringify(new URL("../src/storage.ts", import.meta.url).href)};
|
|
const path = process.argv[1];
|
|
await Promise.all(Array.from({ length: 20 }, () => updateJSON(path, () => ({ count: 0 }), async current => {
|
|
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 5)));
|
|
return { count: current.count + 1 };
|
|
})));
|
|
`;
|
|
|
|
await Promise.all(Array.from({ length: 5 }, () => new Promise<void>((resolve, reject) => {
|
|
const child = spawn(
|
|
process.execPath,
|
|
["--experimental-strip-types", "--input-type=module", "-e", worker, path],
|
|
{ stdio: "inherit" },
|
|
);
|
|
child.on("exit", code => code === 0 ? resolve() : reject(new Error(`child exited ${code}`)));
|
|
child.on("error", reject);
|
|
})));
|
|
|
|
const final = await updateJSON(path, () => ({ count: 0 }), current => current);
|
|
assert.equal(final.count, 100);
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|