CultLib/packages/cultcache-ts/test/cult-cache.test.ts
Meta Vens 8aac0d0b27 cultcache-ts tests: a stamp followed by bytes that are not UTF-8 is a stamp, as a read judges it
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 23:43:37 +02:00

1825 lines
73 KiB
TypeScript

import assert from "node:assert/strict";
import { exec, execFile } from "node:child_process";
import { EventEmitter } from "node:events";
import { existsSync } from "node:fs";
import { access, lstat, mkdir, mkdtemp, readdir, readFile, symlink, writeFile } from "node:fs/promises";
import { homedir, tmpdir } from "node:os";
import { dirname, join, resolve, delimiter } from "node:path";
import { test } from "node:test";
import { promisify } from "node:util";
import { decode, encode } from "@msgpack/msgpack";
import { z } from "zod";
import { CultCache } from "../src/cult-cache";
import { inspectCultCacheBytes } from "../src/cult-cache-inspector";
import { defineDocumentRegistry, defineDocumentType, documentPayloadBytes, documentPayloadFromBytes } from "../src/document";
import { SingleFileMessagePackBackingStore } from "../src/single-file-messagepack-backing-store";
import { refuseUnreadStoreBytes } from "../src/store-format";
import type { CacheBackingStore, CultCacheEnvelope, CultCacheSchema } from "../src/types";
const execFileAsync = promisify(execFile);
const execAsync = promisify(exec);
const cargoCommand = process.env.CARGO ?? (process.platform === "win32" ? join(homedir(), ".cargo", "bin", "cargo.exe") : "cargo");
const dotnetCommand = process.env.DOTNET ?? (process.platform === "win32" ? join("C:", "Program Files", "dotnet", "dotnet.exe") : "dotnet");
const pythonCommand = process.env.PYTHON ?? "python";
const cultCacheTsRoot = resolve(__dirname, "../..");
const cultLibRoot = findAncestor(cultCacheTsRoot, "CultLib.sln") ?? resolve(cultCacheTsRoot, "..", "CultLib");
const cultcachePyRoot = resolve(cultLibRoot, "packages", "cultcache-py");
const cultcachePySrc = ["cultcache-py", "cultnet-py", "cultmesh-py"]
.map((name) => resolve(cultLibRoot, "packages", name, "src"))
.join(delimiter);
const cultcacheRsRoot = existsSync(resolve(cultLibRoot, "packages", "cultcache-rs"))
? resolve(cultLibRoot, "packages", "cultcache-rs")
: resolve(cultCacheTsRoot, "..", "cultcache-rs");
const rustInteropBinary = resolve(
cultcacheRsRoot,
"target",
"debug",
"examples",
process.platform === "win32" ? "cultcache_interop.exe" : "cultcache_interop",
);
const csharpInteropProject = resolve(
cultLibRoot,
"tests",
"GameCult.Caching.InteropPeer",
"GameCult.Caching.InteropPeer.csproj",
);
const csharpInteropDll = resolve(
cultLibRoot,
"bin",
"GameCult.Caching.InteropPeer",
"Debug",
"net10.0",
"GameCult.Caching.InteropPeer.dll",
);
function findAncestor(start: string, marker: string): string | undefined {
let current = start;
while (true) {
if (existsSync(resolve(current, marker))) {
return current;
}
const parent = dirname(current);
if (parent === current) {
return undefined;
}
current = parent;
}
}
test("CultCache supports registry bootstrap, global documents, and lookup by name and index", async () => {
const itemDocument = defineDocumentType({
type: "item",
schema: z.object({
name: z.string(),
category: z.string(),
value: z.number().int(),
}),
name: "name",
indexes: {
category: "category",
},
});
const settingsDocument = defineDocumentType({
type: "settings",
schema: z.object({
theme: z.string(),
retries: z.number().int().nonnegative(),
}),
global: true,
});
const storePath = join(await mkdtemp(join(tmpdir(), "cultcache-ts-")), "cache.msgpack");
const registry = defineDocumentRegistry(itemDocument, settingsDocument);
const cache = CultCache.builder()
.withRegistry(registry)
.withGenericStore(new SingleFileMessagePackBackingStore(storePath))
.build();
await cache.put(itemDocument, "item:potion", {
name: "Potion",
category: "Consumable",
value: 50,
});
await cache.putGlobal(settingsDocument, {
theme: "ash",
retries: 3,
});
assert.deepEqual(cache.getByName(itemDocument, "Potion"), {
name: "Potion",
category: "Consumable",
value: 50,
});
assert.equal(cache.getKeyByIndex(itemDocument, "category", "Consumable"), "item:potion");
assert.deepEqual(cache.getGlobal(settingsDocument), {
theme: "ash",
retries: 3,
});
const snapshot = cache.snapshot();
assert.equal(snapshot.length, 2);
assert.ok(snapshot.every((entry) => entry.payload instanceof Uint8Array && entry.payload.length > 0));
const reloaded = CultCache.builder()
.withRegistry(registry)
.withGenericStore(new SingleFileMessagePackBackingStore(storePath))
.build();
await reloaded.pullAllBackingStores();
assert.deepEqual(reloaded.getByName(itemDocument, "Potion"), {
name: "Potion",
category: "Consumable",
value: 50,
});
assert.deepEqual(reloaded.getRequiredGlobal(settingsDocument), {
theme: "ash",
retries: 3,
});
});
test("CultCache can register name and index lookups after entries already exist", async () => {
const noteDocument = defineDocumentType({
type: "note",
schema: z.object({
title: z.string(),
author: z.string(),
body: z.string(),
}),
});
const storePath = join(await mkdtemp(join(tmpdir(), "cultcache-ts-")), "cache.msgpack");
const cache = CultCache.builder()
.withDocumentType(noteDocument)
.withGenericStore(new SingleFileMessagePackBackingStore(storePath))
.build();
await cache.put(noteDocument, "note:hello", {
title: "Hello",
author: "ari",
body: "world",
});
await cache.registerNameLookup(noteDocument, "title");
await cache.registerIndex(noteDocument, "author", "author");
assert.equal(cache.getIdByName(noteDocument, "Hello"), "note:hello");
assert.equal(cache.getIdByIndex(noteDocument, "author", "ari"), "note:hello");
});
test("CultCache fails closed on unknown or illegal persisted polymorphic state", async () => {
const settingsDocument = defineDocumentType({
type: "settings",
schema: z.object({
theme: z.string(),
}),
global: true,
});
class InMemoryStore implements CacheBackingStore {
constructor(private readonly entries: CultCacheEnvelope[]) {}
async pullAll(): Promise<CultCacheEnvelope[]> {
return this.entries;
}
async push(): Promise<void> {
throw new Error("not used");
}
async delete(): Promise<void> {
throw new Error("not used");
}
}
const cacheWithUnknownType = CultCache.builder()
.withDocumentType(settingsDocument)
.withGenericStore(new InMemoryStore([
{
key: "mystery",
type: "forbidden",
payload: documentPayloadFromBytes(encode({ theme: "ash" })),
storedAt: new Date().toISOString(),
},
]))
.build();
await assert.rejects(
async () => cacheWithUnknownType.pullAllBackingStores(),
/No schema is registered for persisted document type "forbidden"\./,
);
const cacheWithDuplicateGlobal = CultCache.builder()
.withDocumentType(settingsDocument)
.withGenericStore(new InMemoryStore([
{
key: "one",
type: "settings",
payload: documentPayloadFromBytes(encode({ theme: "ash" })),
storedAt: new Date().toISOString(),
},
{
key: "two",
type: "settings",
payload: documentPayloadFromBytes(encode({ theme: "ember" })),
storedAt: new Date().toISOString(),
},
]))
.build();
await assert.rejects(
async () => cacheWithDuplicateGlobal.pullAllBackingStores(),
/has multiple persisted entries/,
);
});
test("CultCache accepts generated parse-style schemas without a Zod mirror", async () => {
type GeneratedSettings = {
schema_version: "generated.settings.v0";
theme: string;
retries: number;
};
const generatedSchema: CultCacheSchema<GeneratedSettings> = {
parse(input: unknown): GeneratedSettings {
if (!input || typeof input !== "object") {
throw new Error("generated settings must be an object");
}
const value = input as Record<string, unknown>;
if (value.schema_version !== "generated.settings.v0") {
throw new Error("generated settings schema_version mismatch");
}
if (typeof value.theme !== "string") {
throw new Error("generated settings theme must be a string");
}
if (typeof value.retries !== "number") {
throw new Error("generated settings retries must be a number");
}
return {
schema_version: "generated.settings.v0",
theme: value.theme,
retries: value.retries,
};
},
};
const generatedDocument = defineDocumentType({
type: "generated-settings",
schema: generatedSchema,
global: true,
});
const storePath = join(await mkdtemp(join(tmpdir(), "cultcache-ts-")), "generated.msgpack");
const cache = CultCache.builder()
.withDocumentType(generatedDocument)
.withGenericStore(new SingleFileMessagePackBackingStore(storePath))
.build();
await cache.putGlobal(generatedDocument, {
schema_version: "generated.settings.v0",
theme: "ash",
retries: 3,
});
const settings = cache.getRequiredGlobal(generatedDocument);
assert.equal(settings.schema_version, "generated.settings.v0");
assert.equal(settings.theme, "ash");
assert.equal(settings.retries, 3);
});
test("CultCache can ingest raw envelopes without re-encoding the payload", async () => {
const noteDocument = defineDocumentType({
type: "note",
schema: z.object({
title: z.string(),
body: z.string(),
}),
});
const storePath = join(await mkdtemp(join(tmpdir(), "cultcache-ts-")), "cache.msgpack");
const origin = CultCache.builder()
.withDocumentType(noteDocument)
.withGenericStore(new SingleFileMessagePackBackingStore(storePath))
.build();
const target = CultCache.builder()
.withDocumentType(noteDocument)
.withGenericStore(new SingleFileMessagePackBackingStore(join(await mkdtemp(join(tmpdir(), "cultcache-ts-")), "target.msgpack")))
.build();
await origin.put(noteDocument, "note:hello", {
title: "Hello",
body: "world",
});
const envelope = origin.getRequiredEnvelope(noteDocument, "note:hello");
const applied = await target.putEnvelope(noteDocument, envelope);
assert.deepEqual(applied, {
title: "Hello",
body: "world",
});
assert.deepEqual(target.getRequired(noteDocument, "note:hello"), applied);
assert.deepEqual(target.getRequiredEnvelope(noteDocument, "note:hello").payload, envelope.payload);
});
test("the cache keeps its own copy of an envelope's bytes: a caller mutating them afterwards changes nothing", async () => {
const noteDocument = defineDocumentType({ type: "note", schema: z.object({ title: z.string(), body: z.string() }) });
const origin = CultCache.builder().withDocumentType(noteDocument)
.withGenericStore(new SingleFileMessagePackBackingStore(join(await mkdtemp(join(tmpdir(), "cultcache-ts-")), "origin.msgpack"))).build();
const target = CultCache.builder().withDocumentType(noteDocument)
.withGenericStore(new SingleFileMessagePackBackingStore(join(await mkdtemp(join(tmpdir(), "cultcache-ts-")), "target.msgpack"))).build();
await origin.put(noteDocument, "note:hello", { title: "Hello", body: "world" });
const envelope = origin.getRequiredEnvelope(noteDocument, "note:hello");
const before = Buffer.from(documentPayloadBytes(envelope.payload));
await target.putEnvelope(noteDocument, envelope);
documentPayloadBytes(envelope.payload).fill(0);
assert.deepEqual(Buffer.from(documentPayloadBytes(target.getRequiredEnvelope(noteDocument, "note:hello").payload)), before);
const read = target.getRequiredEnvelope(noteDocument, "note:hello");
documentPayloadBytes(read.payload).fill(0);
assert.deepEqual(Buffer.from(documentPayloadBytes(target.getRequiredEnvelope(noteDocument, "note:hello").payload)), before);
assert.deepEqual(target.getRequired(noteDocument, "note:hello"), { title: "Hello", body: "world" });
});
test("SingleFileMessagePackBackingStore writes the CultCache v1 snapshot shape", async () => {
const namedDocument = defineDocumentType({
type: "tests.named_entry",
schema: z.object({
Name: z.string(),
Value: z.string(),
}),
schemaId: "sha256:e7b97801b94190f3159012ede45b0069bb09ebf7920f7432c971bc86a0e08de8",
schemaName: "tests.named_entry",
schemaVersion: "tests.named_entry.v1",
contentHash: "sha256:23150930afcc1d84f0cb3012ccc2debcb9b4685f62083033bbaab0083f1e832e",
canonicalSchemaJson: "{\"schemaName\":\"tests.named_entry\",\"schemaVersion\":\"tests.named_entry.v1\",\"members\":[{\"slot\":0,\"name\":\"Name\",\"type\":\"System.String\",\"isReference\":false,\"many\":false,\"targetSchemaName\":null,\"indexAlias\":null,\"isName\":true},{\"slot\":1,\"name\":\"Value\",\"type\":\"System.String\",\"isReference\":false,\"many\":false,\"targetSchemaName\":null,\"indexAlias\":null,\"isName\":false}]}",
members: [
{
slot: 0,
memberName: "Name",
typeName: "System.String",
isName: true,
},
{
slot: 1,
memberName: "Value",
typeName: "System.String",
},
],
name: "Name",
});
const storePath = join(await mkdtemp(join(tmpdir(), "cultcache-ts-")), "snapshot.msgpack");
const cache = CultCache.builder()
.withDocumentType(namedDocument)
.withGenericStore(new SingleFileMessagePackBackingStore(storePath))
.build();
await cache.put(namedDocument, "record-1", {
Name: "Teeth",
Value: "slot-array",
});
const snapshot = decode(await readFile(storePath)) as unknown[];
assert.equal(snapshot[0], "cultcache.store.v1");
const catalog = snapshot[1] as unknown[][];
assert.equal(catalog.length, 1);
assert.equal(catalog[0]?.[0], "sha256:e7b97801b94190f3159012ede45b0069bb09ebf7920f7432c971bc86a0e08de8");
assert.equal(catalog[0]?.[1], "tests.named_entry");
assert.equal(catalog[0]?.[2], "tests.named_entry.v1");
const records = snapshot[2] as unknown[][];
assert.equal(records.length, 1);
assert.equal(records[0]?.[0], "record-1");
assert.equal(records[0]?.[1], "sha256:e7b97801b94190f3159012ede45b0069bb09ebf7920f7432c971bc86a0e08de8");
assert.ok(records[0]?.[3] instanceof Uint8Array);
});
test("SingleFileMessagePackBackingStore reads CultCache v1 snapshots by schema id", async () => {
const noteDocument = defineDocumentType({
type: "tests.named_entry",
schema: z.object({
Name: z.string(),
Value: z.string(),
}),
schemaId: "schema-1",
schemaName: "tests.named_entry",
schemaVersion: "tests.named_entry.v1",
contentHash: "hash-1",
canonicalSchemaJson: "{\"fields\":2}",
});
const storePath = join(await mkdtemp(join(tmpdir(), "cultcache-ts-")), "csharp.msgpack");
await writeFile(
storePath,
encode([
"cultcache.store.v1",
[
[
"schema-1",
"tests.named_entry",
"tests.named_entry.v1",
"hash-1",
"{\"fields\":2}",
["schema-1"],
[],
],
],
[
[
"record-1",
"schema-1",
"2026-05-08T12:00:00Z",
encode({
Name: "Teeth",
Value: "slot-array",
}),
],
],
]),
);
const cache = CultCache.builder()
.withDocumentType(noteDocument)
.withGenericStore(new SingleFileMessagePackBackingStore(storePath))
.build();
await cache.pullAllBackingStores();
assert.deepEqual(cache.getRequired(noteDocument, "record-1"), {
Name: "Teeth",
Value: "slot-array",
});
assert.equal(cache.getRequiredEnvelope(noteDocument, "record-1").schemaId, "schema-1");
});
test("SingleFileMessagePackBackingStore recovers schema-stamped records missing catalog entries", async () => {
const stampedDocument = defineDocumentType({
type: "runtime-policy",
schema: z.tuple([
z.literal("tests.schema_stamped_entry.v1"),
z.string(),
z.string(),
]),
schemaId: "schema-current",
schemaName: "tests.schema_stamped_entry",
schemaVersion: "tests.schema_stamped_entry.v1",
compatibleSchemaIds: ["schema-current"],
});
const storePath = join(await mkdtemp(join(tmpdir(), "cultcache-ts-")), "missing-catalog.msgpack");
await writeFile(
storePath,
encode([
"cultcache.store.v1",
[],
[
[
"record-1",
"sha256:stale-schema-id-from-cold-record",
"2026-06-25T12:00:00Z",
encode([
"tests.schema_stamped_entry.v1",
"schema-stamped",
"still readable",
]),
],
],
]),
);
const cache = CultCache.builder()
.withDocumentType(stampedDocument)
.withGenericStore(new SingleFileMessagePackBackingStore(storePath))
.build();
await cache.pullAllBackingStores();
assert.deepEqual(cache.getRequired(stampedDocument, "record-1"), [
"tests.schema_stamped_entry.v1",
"schema-stamped",
"still readable",
]);
assert.equal(
cache.getRequiredEnvelope(stampedDocument, "record-1").schemaId,
"sha256:stale-schema-id-from-cold-record",
);
});
test("SingleFileMessagePackBackingStore heals legacy envelopes whose payload was persisted as an object", async () => {
const noteDocument = defineDocumentType({
type: "note",
schema: z.object({
title: z.string(),
body: z.string(),
}),
});
const storePath = join(await mkdtemp(join(tmpdir(), "cultcache-ts-")), "legacy.msgpack");
const envelopePayload = {
title: "Hello",
body: "world",
};
await writeFile(
storePath,
encode([
{
key: "note:hello",
type: "note",
payload: envelopePayload,
storedAt: new Date().toISOString(),
},
]),
);
const cache = CultCache.builder()
.withDocumentType(noteDocument)
.withGenericStore(new SingleFileMessagePackBackingStore(storePath))
.build();
await cache.pullAllBackingStores();
assert.deepEqual(cache.getRequired(noteDocument, "note:hello"), envelopePayload);
const rewritten = decode(await readFile(storePath)) as unknown[];
assert.equal(rewritten[0], "cultcache.store.v1");
const records = rewritten[2] as unknown[][];
assert.equal(records.length, 1);
assert.ok(records[0]?.[3] instanceof Uint8Array);
});
test("CultCache rejects a second generic backing store", async () => {
const cache = new CultCache();
await cache.addGenericBackingStore(new SingleFileMessagePackBackingStore(join(tmpdir(), "unused-a.cc")));
await assert.rejects(
cache.addGenericBackingStore(new SingleFileMessagePackBackingStore(join(tmpdir(), "unused-b.cc"))),
/second generic store/u,
);
});
test("CultCache rejects a type registered to two stores", async () => {
const cache = new CultCache();
await cache.addBackingStore(new SingleFileMessagePackBackingStore(join(tmpdir(), "unused-a.cc")), "item");
await assert.rejects(
cache.addBackingStore(new SingleFileMessagePackBackingStore(join(tmpdir(), "unused-b.cc")), "settings", "item"),
/"item" is already routed/u,
);
// A refused registration claims nothing, so "settings" is still free.
await cache.addBackingStore(new SingleFileMessagePackBackingStore(join(tmpdir(), "unused-c.cc")), "settings");
});
test("CultCache routes each type to its home store", async () => {
const itemDocument = defineDocumentType({
type: "item",
schema: z.object({ name: z.string() }),
});
const settingsDocument = defineDocumentType({
type: "settings",
schema: z.object({ theme: z.string() }),
});
const tempDir = await mkdtemp(join(tmpdir(), "cultcache-routes-"));
const genericPath = join(tempDir, "generic.cc");
const settingsPath = join(tempDir, "settings.cc");
const build = () => CultCache.builder()
.withRegistry(defineDocumentRegistry(itemDocument, settingsDocument))
.withBackingStore(new SingleFileMessagePackBackingStore(settingsPath), settingsDocument)
.withGenericStore(new SingleFileMessagePackBackingStore(genericPath))
.build();
const keysIn = async (path: string) =>
(await new SingleFileMessagePackBackingStore(path).pullAll()).map((entry) => `${entry.type}:${entry.key}`);
const cache = build();
await cache.put(itemDocument, "potion", { name: "Potion" });
await cache.put(settingsDocument, "app", { theme: "ash" });
assert.deepEqual(await keysIn(genericPath), ["item:potion"]);
assert.deepEqual(await keysIn(settingsPath), ["settings:app"]);
await cache.delete(settingsDocument, "app");
assert.deepEqual(await keysIn(settingsPath), []);
assert.deepEqual(await keysIn(genericPath), ["item:potion"]);
const reloaded = build();
await reloaded.pullAllBackingStores();
assert.deepEqual(reloaded.getRequired(itemDocument, "potion"), { name: "Potion" });
assert.equal(reloaded.get(settingsDocument, "app"), undefined);
});
test("CultCache refuses an attach that would move a held record's home", async () => {
const settingsDocument = defineDocumentType({ type: "settings", schema: z.object({ theme: z.string() }) });
const tempDir = await mkdtemp(join(tmpdir(), "cultcache-home-move-"));
const genericPath = join(tempDir, "generic.cc");
const settingsPath = join(tempDir, "settings.cc");
const cache = CultCache.builder()
.withRegistry(defineDocumentRegistry(settingsDocument))
.withGenericStore(new SingleFileMessagePackBackingStore(genericPath))
.build();
await cache.put(settingsDocument, "app", { theme: "v1-generic" });
await assert.rejects(
async () => cache.addBackingStore(new SingleFileMessagePackBackingStore(settingsPath), settingsDocument),
/would move "settings" from the generic store to the store routed to settings/u,
);
// Nothing attached: the next write still lands in the generic store, and the refused store stays untouched.
await cache.put(settingsDocument, "app", { theme: "v2-generic" });
const keysIn = async (path: string) =>
(await new SingleFileMessagePackBackingStore(path).pullAll()).map((entry) => `${entry.type}:${entry.key}`);
assert.deepEqual(await keysIn(genericPath), ["settings:app"]);
assert.equal(existsSync(settingsPath), false);
});
test("CultCache refuses at load a record delivered by a store that is not its home", async () => {
const settingsDocument = defineDocumentType({ type: "settings", schema: z.object({ theme: z.string() }) });
const tempDir = await mkdtemp(join(tmpdir(), "cultcache-load-home-"));
const genericPath = join(tempDir, "generic.cc");
const settingsPath = join(tempDir, "settings.cc");
const writer = CultCache.builder()
.withRegistry(defineDocumentRegistry(settingsDocument))
.withGenericStore(new SingleFileMessagePackBackingStore(genericPath))
.build();
await writer.put(settingsDocument, "app", { theme: "stray" });
const genericBytes = await readFile(genericPath);
const cache = CultCache.builder()
.withRegistry(defineDocumentRegistry(settingsDocument))
.withBackingStore(new SingleFileMessagePackBackingStore(settingsPath), settingsDocument)
.withGenericStore(new SingleFileMessagePackBackingStore(genericPath))
.build();
// The cache already holds a record, in its home store, before the refused load.
await cache.put(settingsDocument, "held", { theme: "home" });
const before = cache.snapshot();
const settingsBytes = await readFile(settingsPath);
await assert.rejects(
cache.pullAllBackingStores(),
/"settings" record "app" was loaded from the generic store, but its home is the store routed to settings/u,
);
assert.equal(cache.get(settingsDocument, "app"), undefined);
assert.deepEqual(cache.snapshot(), before);
assert.deepEqual(cache.getRequired(settingsDocument, "held"), { theme: "home" });
assert.deepEqual(await readFile(genericPath), genericBytes);
assert.deepEqual(await readFile(settingsPath), settingsBytes);
});
async function storeState(path: string): Promise<string[]> {
if (!existsSync(path)) {
return [];
}
return (await new SingleFileMessagePackBackingStore(path).pullAll())
.map((entry) => `${entry.type}:${entry.key}:${Buffer.from(documentPayloadBytes(entry.payload)).toString("hex")}`)
.sort();
}
test("putWithThrowingAccessorChangesNeitherStoreNorCache", async () => {
const itemDocument = defineDocumentType({
type: "item",
schema: z.object({ n: z.string() }),
name: (value: { n: string }) => {
if (value.n === "bad") {
throw new Error("accessor refuses bad");
}
return value.n;
},
});
const storePath = join(await mkdtemp(join(tmpdir(), "cultcache-put-accessor-")), "generic.cc");
const cache = CultCache.builder()
.withRegistry(defineDocumentRegistry(itemDocument))
.withGenericStore(new SingleFileMessagePackBackingStore(storePath))
.build();
await cache.put(itemDocument, "k", { n: "good" });
const storeBefore = await storeState(storePath);
const cacheBefore = cache.snapshot();
await assert.rejects(cache.put(itemDocument, "k", { n: "bad" }), /accessor refuses bad/u);
assert.deepEqual(await storeState(storePath), storeBefore);
assert.deepEqual(cache.snapshot(), cacheBefore);
assert.deepEqual(cache.getRequired(itemDocument, "k"), { n: "good" });
assert.equal(cache.getKeyByName(itemDocument, "good"), "k");
});
test("overwriteUnderRegisteredAccessorThatNowThrowsChangesNeitherStoreNorCache", async () => {
const itemDocument = defineDocumentType({ type: "item", schema: z.object({ c: z.string() }) });
const storePath = join(await mkdtemp(join(tmpdir(), "cultcache-overwrite-accessor-")), "generic.cc");
const cache = CultCache.builder()
.withRegistry(defineDocumentRegistry(itemDocument))
.withGenericStore(new SingleFileMessagePackBackingStore(storePath))
.build();
await cache.put(itemDocument, "k", { c: "old" });
let refusing = false;
await cache.registerIndex(itemDocument, "category", (value: { c: string }) => {
if (refusing) {
throw new Error("accessor refuses now");
}
return value.c;
});
refusing = true;
const storeBefore = await storeState(storePath);
const cacheBefore = cache.snapshot();
await assert.rejects(cache.put(itemDocument, "k", { c: "new" }), /accessor refuses now/u);
assert.deepEqual(await storeState(storePath), storeBefore);
assert.deepEqual(cache.snapshot(), cacheBefore);
assert.equal(cache.getKeyByIndex(itemDocument, "category", "old"), "k");
// Deleting runs no accessor: it uses the lookup values stored when the record was admitted.
assert.equal(await cache.delete(itemDocument, "k"), true);
assert.equal(cache.getKeyByIndex(itemDocument, "category", "old"), undefined);
});
test("registeringAccessorThatThrowsOnHeldRecordInstallsNothing", async () => {
const itemDocument = defineDocumentType({ type: "item", schema: z.object({ n: z.string() }) });
const storePath = join(await mkdtemp(join(tmpdir(), "cultcache-register-accessor-")), "generic.cc");
const cache = CultCache.builder()
.withRegistry(defineDocumentRegistry(itemDocument))
.withGenericStore(new SingleFileMessagePackBackingStore(storePath))
.build();
await cache.put(itemDocument, "old", { n: "bad" });
await assert.rejects(
async () => cache.registerIndex(itemDocument, "i", (value: { n: string }) => {
if (value.n === "bad") {
throw new Error("index refuses bad");
}
return value.n;
}),
/index refuses bad/u,
);
await cache.put(itemDocument, "old", { n: "fine" });
assert.deepEqual(cache.getRequired(itemDocument, "old"), { n: "fine" });
assert.equal(cache.getKeyByIndex(itemDocument, "i", "fine"), undefined);
});
test("globalReplaceWithFailingStorePushKeepsOldGlobal", async () => {
const settingsDocument = defineDocumentType({
type: "settings",
schema: z.object({ t: z.string() }),
global: true,
});
const storePath = join(await mkdtemp(join(tmpdir(), "cultcache-global-push-")), "generic.cc");
const inner = new SingleFileMessagePackBackingStore(storePath);
let failPush = false;
const store: CacheBackingStore = {
pullAll: () => inner.pullAll(),
delete: (entry) => inner.delete(entry),
pushAll: (entries, options) => inner.pushAll(entries, options),
push: async (entry) => {
if (failPush) {
throw new Error("disk full");
}
return inner.push(entry);
},
};
const cache = CultCache.builder()
.withRegistry(defineDocumentRegistry(settingsDocument))
.withGenericStore(store)
.build();
await cache.putGlobal(settingsDocument, { t: "base" });
const storeBefore = await storeState(storePath);
const cacheBefore = cache.snapshot();
failPush = true;
await assert.rejects(cache.putGlobal(settingsDocument, { t: "new" }), /disk full/u);
await assert.rejects(cache.put(settingsDocument, "other", { t: "new" }), /must use key "__global__"/u);
assert.deepEqual(await storeState(storePath), storeBefore);
assert.deepEqual(cache.snapshot(), cacheBefore);
assert.deepEqual(cache.getRequiredGlobal(settingsDocument), { t: "base" });
});
test("concurrentGlobalPutsCannotBothReachDisk", async () => {
const settingsDocument = defineDocumentType({
type: "settings",
schema: z.object({ t: z.string() }),
global: true,
});
const storePath = join(await mkdtemp(join(tmpdir(), "cultcache-global-race-")), "generic.cc");
const inner = new SingleFileMessagePackBackingStore(storePath);
// The first push after arming writes, then pauses until released. A second write that reaches the
// store during that pause is recorded: serialized writes cannot.
let armed = false;
let paused = false;
let reachedStoreWhilePaused = false;
let release!: () => void;
let reportPaused!: () => void;
const pausedSignal = new Promise<void>((resolve) => {
reportPaused = resolve;
});
const store: CacheBackingStore = {
pullAll: () => inner.pullAll(),
delete: (entry) => inner.delete(entry),
push: async (entry) => {
reachedStoreWhilePaused ||= paused;
await inner.push(entry);
if (armed) {
armed = false;
paused = true;
await new Promise<void>((resolve) => {
release = resolve;
reportPaused();
});
paused = false;
}
},
};
const build = (backing: CacheBackingStore) => CultCache.builder()
.withRegistry(defineDocumentRegistry(settingsDocument))
.withGenericStore(backing)
.build();
const cache = build(store);
await cache.putGlobal(settingsDocument, { t: "base" });
armed = true;
const first = cache.putGlobal(settingsDocument, { t: "first" });
await pausedSignal;
const second = cache.putGlobal(settingsDocument, { t: "second" });
await new Promise((resolve) => setTimeout(resolve, 50));
release();
await Promise.all([first, second]);
assert.equal(reachedStoreWhilePaused, false);
const onDisk = await inner.pullAll();
assert.deepEqual(onDisk.map((entry) => entry.key), [CultCache.GLOBAL_KEY]);
assert.deepEqual(
Buffer.from(documentPayloadBytes(onDisk[0].payload)),
Buffer.from(documentPayloadBytes(cache.getRequiredEnvelope(settingsDocument, CultCache.GLOBAL_KEY).payload)),
);
assert.deepEqual(cache.getRequiredGlobal(settingsDocument), { t: "second" });
const reloaded = build(new SingleFileMessagePackBackingStore(storePath));
await reloaded.pullAllBackingStores();
assert.deepEqual(reloaded.getRequiredGlobal(settingsDocument), { t: "second" });
});
test("unawaitedFailingPutIsAnUnhandledRejection", async () => {
const itemDocument = defineDocumentType({ type: "item", schema: z.object({ n: z.string() }) });
const store: CacheBackingStore = {
pullAll: async () => [],
push: async () => {
throw new Error("disk full");
},
delete: async () => undefined,
};
const cache = CultCache.builder()
.withRegistry(defineDocumentRegistry(itemDocument))
.withGenericStore(store)
.build();
const runnerListeners = process.listeners("unhandledRejection");
process.removeAllListeners("unhandledRejection");
const unhandled: unknown[] = [];
const listener = (reason: unknown) => {
unhandled.push(reason);
};
process.on("unhandledRejection", listener);
try {
void cache.put(itemDocument, "k", { n: "x" });
await new Promise((resolve) => setTimeout(resolve, 50));
} finally {
process.off("unhandledRejection", listener);
for (const runnerListener of runnerListeners) {
process.on("unhandledRejection", runnerListener);
}
}
assert.equal(unhandled.length, 1);
assert.match(String((unhandled[0] as Error).message), /disk full/u);
// The chain survived the rejection: the next operation still runs.
await assert.rejects(cache.put(itemDocument, "k", { n: "y" }), /disk full/u);
});
test("storeChangeHandlerWritingBackQueuesAfterTheOperation", async () => {
const itemDocument = defineDocumentType({ type: "item", schema: z.object({ n: z.string() }) });
const storePath = join(await mkdtemp(join(tmpdir(), "cultcache-writeback-")), "generic.cc");
const inner = new SingleFileMessagePackBackingStore(storePath);
const changes = new EventEmitter();
const pushed: string[] = [];
const store: CacheBackingStore = {
pullAll: () => inner.pullAll(),
delete: (entry) => inner.delete(entry),
push: async (entry) => {
await inner.push(entry);
pushed.push(entry.key);
changes.emit("pushed", entry.key);
},
};
const cache = CultCache.builder()
.withRegistry(defineDocumentRegistry(itemDocument))
.withGenericStore(store)
.build();
// The handler writes back and the store does not wait for it: that write queues behind the put.
const writeBacks: Promise<unknown>[] = [];
changes.on("pushed", (key: string) => {
if (key === "k") {
writeBacks.push(cache.put(itemDocument, "side", { n: "side" }));
}
});
await cache.put(itemDocument, "k", { n: "x" });
await Promise.all(writeBacks);
assert.equal(writeBacks.length, 1);
assert.deepEqual(pushed, ["k", "side"]);
assert.deepEqual(cache.get(itemDocument, "k"), { n: "x" });
assert.deepEqual(cache.get(itemDocument, "side"), { n: "side" });
assert.deepEqual(
(await inner.pullAll()).map((entry) => entry.key).sort(),
cache.snapshot().map((entry) => entry.key).sort(),
);
});
test("legacyKeyGlobalLoadsWithoutWritingAndFirstWriteLeavesOneGlobal", async () => {
const settingsDocument = defineDocumentType({
type: "settings",
schema: z.object({ t: z.string() }),
global: true,
});
const storePath = join(await mkdtemp(join(tmpdir(), "cultcache-legacy-global-")), "generic.cc");
const build = () => CultCache.builder()
.withRegistry(defineDocumentRegistry(settingsDocument))
.withGenericStore(new SingleFileMessagePackBackingStore(storePath))
.build();
const seed = CultCache.builder().withRegistry(defineDocumentRegistry(settingsDocument)).build();
await seed.putGlobal(settingsDocument, { t: "old" });
const legacy = { ...seed.getRequiredEnvelope(settingsDocument, CultCache.GLOBAL_KEY), key: "legacy" };
await new SingleFileMessagePackBackingStore(storePath).push(legacy);
const bytes = await readFile(storePath);
const cache = build();
await cache.pullAllBackingStores();
assert.deepEqual(cache.getRequiredGlobal(settingsDocument), { t: "old" });
assert.deepEqual(cache.get(settingsDocument, CultCache.GLOBAL_KEY), { t: "old" });
assert.deepEqual(await readFile(storePath), bytes);
await cache.putGlobal(settingsDocument, { t: "new" });
assert.deepEqual(
(await new SingleFileMessagePackBackingStore(storePath).pullAll()).map((entry) => entry.key),
[CultCache.GLOBAL_KEY],
);
const reloaded = build();
await reloaded.pullAllBackingStores();
assert.deepEqual(reloaded.getRequiredGlobal(settingsDocument), { t: "new" });
// Deleting an adopted global removes the legacy record.
await new SingleFileMessagePackBackingStore(storePath).pushAll([legacy]);
const deleting = build();
await deleting.pullAllBackingStores();
assert.equal(await deleting.deleteGlobal(settingsDocument), true);
assert.deepEqual(await new SingleFileMessagePackBackingStore(storePath).pullAll(), []);
// Two globals of one type on disk, under any keys, are refused.
await new SingleFileMessagePackBackingStore(storePath).pushAll([legacy, { ...legacy, key: CultCache.GLOBAL_KEY }]);
await assert.rejects(build().pullAllBackingStores(), /has multiple persisted entries/u);
});
test("concurrentPutAndAttachCannotLandRecordInNonHomeStore", async () => {
const settingsDocument = defineDocumentType({ type: "settings", schema: z.object({ t: z.string() }) });
const tempDir = await mkdtemp(join(tmpdir(), "cultcache-put-attach-race-"));
const genericPath = join(tempDir, "generic.cc");
const settingsPath = join(tempDir, "settings.cc");
const cache = CultCache.builder()
.withRegistry(defineDocumentRegistry(settingsDocument))
.withGenericStore(new SingleFileMessagePackBackingStore(genericPath))
.build();
const pending = cache.put(settingsDocument, "app", { t: "x" });
const attach = Promise.resolve().then(() =>
cache.addBackingStore(new SingleFileMessagePackBackingStore(settingsPath), settingsDocument),
);
await pending;
await assert.rejects(attach, /would move "settings" from the generic store/u);
assert.deepEqual(
(await new SingleFileMessagePackBackingStore(genericPath).pullAll()).map((entry) => entry.key),
["app"],
);
assert.equal(existsSync(settingsPath), false);
const reloaded = CultCache.builder()
.withRegistry(defineDocumentRegistry(settingsDocument))
.withGenericStore(new SingleFileMessagePackBackingStore(genericPath))
.build();
await reloaded.pullAllBackingStores();
assert.deepEqual(reloaded.getRequired(settingsDocument, "app"), { t: "x" });
});
test("pullWithDuplicateGlobalLeavesCacheUnchanged", async () => {
const itemDocument = defineDocumentType({ type: "item", schema: z.object({ name: z.string() }) });
const settingsDocument = defineDocumentType({
type: "settings",
schema: z.object({ theme: z.string() }),
global: true,
});
const tempDir = await mkdtemp(join(tmpdir(), "cultcache-duplicate-global-"));
const storePath = join(tempDir, "generic.cc");
const cache = CultCache.builder()
.withRegistry(defineDocumentRegistry(itemDocument, settingsDocument))
.withGenericStore(new SingleFileMessagePackBackingStore(storePath))
.build();
await cache.put(itemDocument, "potion", { name: "Potion" });
await cache.putGlobal(settingsDocument, { theme: "ash" });
const before = cache.snapshot();
// A second record of the global type lands in the store file behind the cache's back.
const stray = cache.getRequiredEnvelope(settingsDocument, CultCache.GLOBAL_KEY);
await new SingleFileMessagePackBackingStore(storePath).push({ ...stray, key: "stray" });
await assert.rejects(cache.pullAllBackingStores(), /has multiple persisted entries/u);
assert.deepEqual(cache.snapshot(), before);
assert.deepEqual(cache.getRequired(itemDocument, "potion"), { name: "Potion" });
assert.deepEqual(cache.getRequiredGlobal(settingsDocument), { theme: "ash" });
});
test("pullWithThrowingIndexAccessorLeavesCacheUnchanged", async () => {
const itemDocument = defineDocumentType({
type: "item",
schema: z.object({ name: z.string(), category: z.string() }),
name: "name",
indexes: {
category: (item: { name: string; category: string }) => {
if (item.name === "Bomb") {
throw new Error("category accessor refuses Bomb");
}
return item.category;
},
},
});
const settingsDocument = defineDocumentType({
type: "settings",
schema: z.object({ theme: z.string() }),
global: true,
});
const tempDir = await mkdtemp(join(tmpdir(), "cultcache-throwing-accessor-"));
const storePath = join(tempDir, "generic.cc");
const cache = CultCache.builder()
.withRegistry(defineDocumentRegistry(itemDocument, settingsDocument))
.withGenericStore(new SingleFileMessagePackBackingStore(storePath))
.build();
await cache.put(itemDocument, "potion", { name: "Potion", category: "consumable" });
await cache.putGlobal(settingsDocument, { theme: "ash" });
const before = cache.snapshot();
// A record whose index accessor throws lands in the store file behind the cache's back.
const potion = cache.getRequiredEnvelope(itemDocument, "potion");
await new SingleFileMessagePackBackingStore(storePath).push({
...potion,
key: "bomb",
payload: documentPayloadFromBytes(encode({ name: "Bomb", category: "explosive" })),
});
await assert.rejects(cache.pullAllBackingStores(), /category accessor refuses Bomb/u);
assert.deepEqual(cache.snapshot(), before);
assert.deepEqual(cache.getRequired(itemDocument, "potion"), { name: "Potion", category: "consumable" });
assert.equal(cache.get(itemDocument, "bomb"), undefined);
assert.equal(cache.getKeyByName(itemDocument, "Potion"), "potion");
assert.equal(cache.getKeyByName(itemDocument, "Bomb"), undefined);
assert.equal(cache.getKeyByIndex(itemDocument, "category", "consumable"), "potion");
assert.deepEqual(cache.getRequiredGlobal(settingsDocument), { theme: "ash" });
});
test("CultCache with zero stores writes and deletes in memory", async () => {
const settingsDocument = defineDocumentType({ type: "settings", schema: z.object({ theme: z.string() }) });
const cache = CultCache.builder().withRegistry(defineDocumentRegistry(settingsDocument)).build();
await cache.put(settingsDocument, "x", { theme: "mem" });
assert.deepEqual(cache.getRequired(settingsDocument, "x"), { theme: "mem" });
assert.equal(await cache.delete(settingsDocument, "x"), true);
assert.equal(cache.get(settingsDocument, "x"), undefined);
});
test("CultCache v1 MessagePack stores are readable across TS, Rust, C#, and Python", async () => {
await buildInteropPeers();
const tempDir = await mkdtemp(join(tmpdir(), "cultcache-interop-"));
const writers = [
{
name: "ts",
write: async (file: string) => writeTsInteropStore(file, "ts-writer"),
},
{
name: "rust",
write: async (file: string) => runJsonCommand("rust-write", rustInteropBinary, [
"write",
"--file", file,
"--runtime-id", "rust-writer",
], cultcacheRsRoot),
},
{
name: "csharp",
write: async (file: string) => runJsonCommand("csharp-write", dotnetCommand, [
csharpInteropDll,
"write",
"--file", file,
"--runtime-id", "csharp-writer",
], cultLibRoot),
},
{
name: "python",
write: async (file: string) => runJsonCommand("python-write", pythonCommand, [
"-m", "cultcache_py.interop",
"write",
"--file", file,
"--runtime-id", "python-writer",
], cultcachePyRoot, { PYTHONPATH: cultcachePySrc }),
},
];
const readers = [
{
name: "ts",
read: async (file: string) => readTsInteropStore(file),
},
{
name: "rust",
read: async (file: string) => runJsonCommand("rust-read", rustInteropBinary, [
"read",
"--file", file,
], cultcacheRsRoot),
},
{
name: "csharp",
read: async (file: string) => runJsonCommand("csharp-read", dotnetCommand, [
csharpInteropDll,
"read",
"--file", file,
], cultLibRoot),
},
{
name: "python",
read: async (file: string) => runJsonCommand("python-read", pythonCommand, [
"-m", "cultcache_py.interop",
"read",
"--file", file,
], cultcachePyRoot, { PYTHONPATH: cultcachePySrc }),
},
];
for (const writer of writers) {
const file = join(tempDir, `${writer.name}.msgpack`);
const written = await writer.write(file);
const decoded = decode(await readFile(file)) as unknown[];
assert.equal(decoded[0], "cultcache.store.v1");
assert.ok(Array.isArray(decoded[1]), `${writer.name} did not write a schema catalog`);
assert.ok(Array.isArray(decoded[2]), `${writer.name} did not write records`);
for (const reader of readers) {
const read = await reader.read(file);
assert.equal(read.documentId, written.documentId, `${reader.name} failed to read ${writer.name}`);
assert.equal(read.authorRuntimeId, written.authorRuntimeId);
assert.equal(read.body, "The v1 store format is the contract.");
assert.ok(read.tags.includes("interop"));
}
}
// C# write-routed attaches one home store per type: each file is a complete v1 snapshot holding only its own records.
const catalogFile = join(tempDir, "catalog.cc");
const runFile = join(tempDir, "run.cc");
const routed = await runJsonCommand("csharp-write-routed", dotnetCommand, [
csharpInteropDll,
"write-routed",
catalogFile,
runFile,
], cultLibRoot);
const recordsIn = async (file: string) => {
const decoded = decode(await readFile(file)) as unknown[];
assert.equal(decoded[0], "cultcache.store.v1");
assert.ok(Array.isArray(decoded[1]), `${file} has no schema catalog`);
return (decoded[2] as unknown[][]).map((record) => ({ schemaId: record[1], key: record[0] }));
};
const catalogRecords = await recordsIn(catalogFile);
const runRecords = await recordsIn(runFile);
assert.deepEqual(catalogRecords.map((record) => record.key), ["note:csharp-routed"]);
assert.deepEqual(runRecords.map((record) => record.key), ["run-note:csharp-routed"]);
assert.notEqual(catalogRecords[0]?.schemaId, runRecords[0]?.schemaId);
for (const reader of readers) {
const read = await reader.read(catalogFile);
assert.equal(read.documentId, routed.documentId, `${reader.name} failed to read the routed catalog store`);
assert.equal(read.body, "One home store per document type.");
}
});
test("CultCache interop reader accepts missing compatible trailing slots and rejects mismatched slots", async () => {
const tempDir = await mkdtemp(join(tmpdir(), "cultcache-interop-"));
const compatible = join(tempDir, "compatible.msgpack");
const mismatch = join(tempDir, "mismatch.msgpack");
await writeTsInteropStore(compatible, "legacy-writer", { legacyPayload: true });
assert.deepEqual(await readTsInteropStore(compatible), {
schemaVersion: "cultcache.interop_note.v1",
documentId: "note:legacy-writer",
authorRuntimeId: "legacy-writer",
title: "legacy-writer wrote a CultCache note",
body: "The v1 store format is the contract.",
tags: [],
});
await writeTsInteropStore(mismatch, "bad-writer", { mismatchedPayload: true });
await assert.rejects(
() => readTsInteropStore(mismatch),
/Expected string/u,
);
});
test("CultCache inspector decodes v1 store catalog and record payloads from bytes", async () => {
const tempDir = await mkdtemp(join(tmpdir(), "cultcache-inspector-"));
const file = join(tempDir, "cache.cc");
const written = await writeTsInteropStore(file, "inspector");
const inspection = inspectCultCacheBytes(file, await readFile(file));
assert.equal(inspection.format, "cultcache.store.v1");
assert.equal(inspection.catalog.length, 1);
assert.equal(inspection.catalog[0]?.schemaName, "cultcache.interop-note");
assert.equal(inspection.records.length, 1);
assert.equal(inspection.records[0]?.key, written.documentId);
assert.deepEqual(inspection.records[0]?.payloadPreview, [
"cultcache.interop_note.v1",
written.documentId,
written.authorRuntimeId,
written.title,
written.body,
written.tags,
]);
});
test("CultCache inspector recovers schema-stamped records missing catalog entries", async () => {
const tempDir = await mkdtemp(join(tmpdir(), "cultcache-inspector-"));
const file = join(tempDir, "missing-catalog.cc");
await writeFile(
file,
encode([
"cultcache.store.v1",
[],
[
[
"record-1",
"sha256:stale-schema-id-from-cold-record",
"2026-06-25T12:00:00Z",
encode([
"tests.schema_stamped_entry.v1",
"schema-stamped",
"still readable",
]),
],
],
]),
);
const inspection = inspectCultCacheBytes(file, await readFile(file));
assert.equal(inspection.catalog.length, 1);
assert.equal(inspection.catalog[0]?.schemaId, "sha256:stale-schema-id-from-cold-record");
assert.equal(inspection.catalog[0]?.schemaName, "tests.schema_stamped_entry");
assert.equal(inspection.records[0]?.schemaName, "tests.schema_stamped_entry");
assert.deepEqual(inspection.records[0]?.payloadPreview, [
"tests.schema_stamped_entry.v1",
"schema-stamped",
"still readable",
]);
});
interface InteropNote {
schemaVersion: "cultcache.interop_note.v1";
documentId: string;
authorRuntimeId: string;
title: string;
body: string;
tags: string[];
}
const interopNoteDocument = defineDocumentType({
type: "cultcache.interop-note",
schemaId: "cultcache.interop-note",
schemaName: "cultcache.interop-note",
schemaVersion: "cultcache.interop_note.v1",
contentHash: "cultcache.interop-note",
canonicalSchemaJson: "{\"schemaName\":\"cultcache.interop-note\",\"schemaVersion\":\"cultcache.interop_note.v1\",\"members\":[{\"slot\":0,\"name\":\"SchemaVersion\",\"type\":\"System.String\",\"isReference\":false,\"many\":false,\"targetSchemaName\":null,\"indexAlias\":null,\"isName\":false},{\"slot\":1,\"name\":\"DocumentId\",\"type\":\"System.String\",\"isReference\":false,\"many\":false,\"targetSchemaName\":null,\"indexAlias\":null,\"isName\":true},{\"slot\":2,\"name\":\"AuthorRuntimeId\",\"type\":\"System.String\",\"isReference\":false,\"many\":false,\"targetSchemaName\":null,\"indexAlias\":null,\"isName\":false},{\"slot\":3,\"name\":\"Title\",\"type\":\"System.String\",\"isReference\":false,\"many\":false,\"targetSchemaName\":null,\"indexAlias\":null,\"isName\":false},{\"slot\":4,\"name\":\"Body\",\"type\":\"System.String\",\"isReference\":false,\"many\":false,\"targetSchemaName\":null,\"indexAlias\":null,\"isName\":false},{\"slot\":5,\"name\":\"Tags\",\"type\":\"System.String[]\",\"isReference\":false,\"many\":false,\"targetSchemaName\":null,\"indexAlias\":null,\"isName\":false}]}",
compatibleSchemaIds: ["cultcache.interop-note"],
members: [
{ slot: 0, memberName: "SchemaVersion", typeName: "System.String" },
{ slot: 1, memberName: "DocumentId", typeName: "System.String", isName: true },
{ slot: 2, memberName: "AuthorRuntimeId", typeName: "System.String" },
{ slot: 3, memberName: "Title", typeName: "System.String" },
{ slot: 4, memberName: "Body", typeName: "System.String" },
{ slot: 5, memberName: "Tags", typeName: "System.String[]" },
],
schema: z.object({
schemaVersion: z.literal("cultcache.interop_note.v1"),
documentId: z.string().min(1),
authorRuntimeId: z.string().min(1),
title: z.string().min(1),
body: z.string().min(1),
tags: z.array(z.string().min(1)),
}),
formatter: {
encode(value: InteropNote): Uint8Array {
return encode([
value.schemaVersion,
value.documentId,
value.authorRuntimeId,
value.title,
value.body,
value.tags,
]);
},
decode(payload: Uint8Array): InteropNote {
const decoded = decode(payload);
if (!Array.isArray(decoded) || decoded.length < 5) {
throw new Error("CultCache interop note payload must be a MessagePack slot array.");
}
const [schemaVersion, documentId, authorRuntimeId, title, body, tags] = decoded;
return interopNoteDocument.schema.parse({
schemaVersion,
documentId,
authorRuntimeId,
title,
body,
tags: Array.isArray(tags) ? tags : [],
});
},
},
});
async function writeTsInteropStore(
file: string,
runtimeId: string,
options: { legacyPayload?: boolean; mismatchedPayload?: boolean } = {},
): Promise<InteropNote> {
const cache = CultCache.builder()
.withDocumentType(interopNoteDocument)
.withGenericStore(new SingleFileMessagePackBackingStore(file))
.build();
const note: InteropNote = {
schemaVersion: "cultcache.interop_note.v1",
documentId: `note:${runtimeId}`,
authorRuntimeId: runtimeId,
title: `${runtimeId} wrote a CultCache note`,
body: "The v1 store format is the contract.",
tags: [runtimeId, "ts", "interop"],
};
if (options.legacyPayload || options.mismatchedPayload) {
const payload = options.mismatchedPayload
? encode([note.schemaVersion, note.documentId, 42, note.title, note.body, note.tags])
: encode([note.schemaVersion, note.documentId, note.authorRuntimeId, note.title, note.body]);
await new SingleFileMessagePackBackingStore(file).push({
key: note.documentId,
type: interopNoteDocument.type,
schemaId: interopNoteDocument.schemaId,
catalogEntry: {
schemaId: interopNoteDocument.schemaId!,
schemaName: interopNoteDocument.schemaName!,
schemaVersion: interopNoteDocument.schemaVersion!,
contentHash: interopNoteDocument.contentHash!,
canonicalSchemaJson: interopNoteDocument.canonicalSchemaJson!,
compatibleSchemaIds: [interopNoteDocument.schemaId!],
members: interopNoteDocument.members,
},
storedAt: new Date().toISOString(),
payload: documentPayloadFromBytes(payload),
});
return note;
}
await cache.put(interopNoteDocument, note.documentId, note);
return note;
}
async function readTsInteropStore(file: string): Promise<InteropNote> {
const cache = CultCache.builder()
.withDocumentType(interopNoteDocument)
.withGenericStore(new SingleFileMessagePackBackingStore(file))
.build();
await cache.pullAllBackingStores();
const notes = cache.getAll(interopNoteDocument);
const note = notes[0];
if (!note) {
throw new Error("No cultcache.interop-note records found.");
}
return note;
}
async function buildInteropPeers(): Promise<void> {
if (!(await exists(rustInteropBinary))) {
await execAsync(`"${cargoCommand}" build --quiet --example cultcache_interop`, {
cwd: cultcacheRsRoot,
});
}
if (!(await exists(csharpInteropDll))) {
await execAsync(`"${dotnetCommand}" build "${csharpInteropProject}" -nologo`, {
cwd: cultLibRoot,
});
}
}
async function exists(path: string): Promise<boolean> {
try {
await access(path);
return true;
} catch {
return false;
}
}
async function runJsonCommand(
name: string,
command: string,
args: string[],
cwd: string,
env: NodeJS.ProcessEnv = {},
): Promise<any> {
const { stdout, stderr } = await execFileAsync(command, args, {
cwd,
env: { ...process.env, ...env },
timeout: 30_000,
});
const trimmed = stdout.trim();
if (!trimmed) {
throw new Error(`${name} produced no stdout.\n${stderr}`);
}
return JSON.parse(trimmed.split(/\r?\n/).at(-1) as string);
}
// Shared refusal vectors: tests/vectors/document-variants-c0, read by every runtime's tests.
const variantVectors = resolve(cultLibRoot, "tests", "vectors", "document-variants-c0");
const itemSchemaId = "sha256:88d3fdf0a927acf3b163940d8f8c7fe62b3316542ce771a67ec8bc038f594788";
async function pullVector(name: string): Promise<CultCacheEnvelope[]> {
const dir = await mkdtemp(join(tmpdir(), "cultcache-vector-"));
const file = join(dir, "store.msgpack");
await writeFile(file, await readFile(join(variantVectors, name)));
return new SingleFileMessagePackBackingStore(file).pullAll();
}
test("SingleFileMessagePackBackingStore refuses an unknown store header by name", async () => {
await assert.rejects(() => pullVector("unknown-header.msgpack"), /cultcache\.store\.v9/u);
});
test("SingleFileMessagePackBackingStore refuses an extra record slot naming the record", async () => {
await assert.rejects(
() => pullVector("extra-slot-full-payload.msgpack"),
(error: Error) => error.message.includes("item:anvil") && error.message.includes(itemSchemaId),
);
});
test("SingleFileMessagePackBackingStore refuses a variant store by version or record", async () => {
await assert.rejects(
() => pullVector("variant-v2.msgpack"),
(error: Error) => error.message.includes("cultcache.store.v2") || error.message.includes("item:anvil-big"),
);
});
test("SingleFileMessagePackBackingStore refuses the C#-written variant store by version or record", async () => {
await assert.rejects(
() => pullVector("../document-variants-c1/variant-store.msgpack"),
(error: Error) => error.message.includes("cultcache.store.v2") || error.message.includes("laser-big"),
);
});
test("SingleFileMessagePackBackingStore refusals say what was found without blaming variants", async () => {
await assert.rejects(
() => pullVector("unknown-header.msgpack"),
(error: Error) => error.message.includes("not one this runtime reads") && !error.message.includes("variant"),
);
await assert.rejects(
() => pullVector("extra-slot-full-payload.msgpack"),
(error: Error) => error.message.includes("not a valid store") && !error.message.includes("variant"),
);
});
test("pushAll refuses a store in a format this runtime does not read and leaves it byte-identical", async () => {
const vector = await readFile(resolve(cultLibRoot, "tests", "vectors", "document-variants-c2a", "v3-base.msgpack"));
const envelope = { key: "probe", type: "probe", payload: documentPayloadFromBytes(new Uint8Array([0x80])), storedAt: "2026-10-22T00:00:00Z" };
for (const bytes of [vector, vector.subarray(0, Math.floor(vector.length / 2))]) {
const file = join(await mkdtemp(join(tmpdir(), "cultcache-pushall-v3-")), "store.msgpack");
await writeFile(file, bytes);
await assert.rejects(() => new SingleFileMessagePackBackingStore(file).pushAll([envelope]), /cultcache\.store\.v3/u);
assert.equal(Buffer.compare(await readFile(file), bytes), 0);
}
const healed = join(await mkdtemp(join(tmpdir(), "cultcache-pushall-heal-")), "store.msgpack");
await writeFile(healed, new Uint8Array([0x93, 0xa0]));
const store = new SingleFileMessagePackBackingStore(healed);
await store.pushAll([envelope]);
assert.equal((await store.pullAll()).length, 1);
});
function headerRefused(bytes: Uint8Array): boolean {
try {
refuseUnreadStoreBytes(bytes);
return false;
} catch (error) {
assert.match((error as Error).message, /not one this runtime reads/u);
return true;
}
}
function storeStart(arrayLead: number[], text: string, strForm: "fix" | "8" | "16" | "32"): Uint8Array {
const body = Buffer.from(text, "utf8");
const n = body.length;
const tag =
strForm === "fix" ? [0xa0 | n]
: strForm === "8" ? [0xd9, n]
: strForm === "16" ? [0xda, n >> 8, n & 0xff]
: [0xdb, 0, 0, n >> 8, n & 0xff];
return new Uint8Array([...arrayLead, ...tag, ...body]);
}
test("a store's header is judged from its first bytes, in every MessagePack array and string form", () => {
const leads = [[0x91], [0x9f], [0xdc, 0x00, 0x01], [0xdc, 0x01, 0x00], [0xdd, 0x00, 0x00, 0x00, 0x01], [0xdd, 0x00, 0x01, 0x00, 0x00]];
for (const lead of leads) {
for (const form of ["fix", "8", "16", "32"] as const) {
const v3 = storeStart(lead, "cultcache.store.v3", form);
assert.equal(headerRefused(v3), true, `${lead} ${form} v3`);
assert.equal(headerRefused(v3.subarray(0, v3.length - 1)), false, `${lead} ${form} v3 one byte short`);
assert.equal(headerRefused(storeStart(lead, "cultcache.store.v1", form)), false, `${lead} ${form} v1`);
assert.equal(headerRefused(storeStart(lead, "other.store.v3", form)), false, `${lead} ${form} other`);
assert.equal(headerRefused(storeStart(lead, "", form)), false, `${lead} ${form} empty`);
}
}
// The longest fixstr, and a string long enough to need str16.
assert.equal(headerRefused(storeStart([0x91], `cultcache.store.v3${"x".repeat(13)}`, "fix")), true);
assert.equal(headerRefused(storeStart([0x91], `cultcache.store.v3${"x".repeat(300)}`, "16")), true);
assert.equal(headerRefused(storeStart([0x91], `cultcache.store.v3${"x".repeat(300)}`, "32")), true);
// A view into a larger buffer is read from its own start.
const padded = new Uint8Array([9, 9, ...storeStart([0x91], "cultcache.store.v3", "fix")]);
assert.equal(headerRefused(padded.subarray(2)), true);
});
test("a stamp followed by bytes that are not UTF-8 gets the verdict a read gives", async () => {
const stamp = [...Buffer.from("cultcache.store.v3", "utf8")];
const forms = { fixstr: [0x91, 0xb3, ...stamp, 0xff], str8: [0x91, 0xd9, 19, ...stamp, 0xff] };
const envelope = { key: "probe", type: "probe", payload: documentPayloadFromBytes(new Uint8Array([0x80])), storedAt: "2026-10-22T00:00:00Z" };
for (const [name, bytes] of Object.entries(forms)) {
const file = join(await mkdtemp(join(tmpdir(), "cultcache-stamp-")), "store.msgpack");
await writeFile(file, new Uint8Array(bytes));
const store = new SingleFileMessagePackBackingStore(file);
await assert.rejects(() => store.pullAll(), /not one this runtime reads/u, `${name} read`);
await assert.rejects(() => store.pushAll([envelope]), /not one this runtime reads/u, `${name} pushAll`);
assert.deepEqual([...(await readFile(file))], bytes, `${name} left as it was`);
}
});
test("bytes with no readable store stamp are not refused", () => {
const stamp = [...Buffer.from("cultcache.store.v3", "utf8")];
const cases: Record<string, number[]> = {
empty: [],
"empty array": [0x90],
"empty array16": [0xdc, 0x00, 0x00, 0xb2, ...stamp],
"empty array32": [0xdd, 0x00, 0x00, 0x00, 0x00, 0xb2, ...stamp],
"array16 cut before its count": [0xdc, 0x00],
"array32 cut before its count": [0xdd, 0x00, 0x00, 0x00],
"array with no element": [0x91],
"first element is not a string": [0x92, 0x01, 0xb2, ...stamp],
"a bare string": [0xb2, ...stamp],
"a map": [0x81, 0xb2, ...stamp],
"a map whose bytes read as an array32 count": [0x81, 0x00, 0x00, 0x00, 0x01, 0xb2, ...stamp],
"a small integer before a stamp-looking body": [0x91, 0x12, ...stamp],
"a uint32 before a stamp-looking body": [0x91, 0xce, 0x00, 0x00, 0x00, 0x12, ...stamp],
"str8 cut before its length": [0x91, 0xd9],
"str16 cut before its length": [0x91, 0xda, 0x00],
"str32 cut before its length": [0x91, 0xdb, 0x00, 0x00, 0x00],
};
for (const [name, bytes] of Object.entries(cases)) {
assert.equal(headerRefused(new Uint8Array(bytes)), false, name);
}
});
test("pushAll leaves a store in a newer format alone when soft, and replaces a store it reads", async () => {
const vector = await readFile(resolve(cultLibRoot, "tests", "vectors", "document-variants-c2a", "v3-base.msgpack"));
const envelope = { key: "probe", type: "probe", payload: documentPayloadFromBytes(new Uint8Array([0x80])), storedAt: "2026-10-22T00:00:00Z" };
const newer = join(await mkdtemp(join(tmpdir(), "cultcache-pushall-soft-")), "store.msgpack");
await writeFile(newer, vector);
await new SingleFileMessagePackBackingStore(newer).pushAll([envelope], { soft: true });
assert.equal(Buffer.compare(await readFile(newer), vector), 0);
const own = join(await mkdtemp(join(tmpdir(), "cultcache-pushall-own-")), "store.msgpack");
const store = new SingleFileMessagePackBackingStore(own);
await store.pushAll([envelope]);
await store.pushAll([{ ...envelope, key: "second" }]);
assert.deepEqual((await store.pullAll()).map((entry) => entry.key), ["second"]);
});
test("SingleFileMessagePackBackingStore reads only a missing store as empty", async () => {
const dir = await mkdtemp(join(tmpdir(), "cultcache-absent-"));
assert.deepEqual(await new SingleFileMessagePackBackingStore(join(dir, "store.cc")).pullAll(), []);
// An existing empty file is not a store: no CultCache writer leaves one.
const empty = join(dir, "empty.cc");
await writeFile(empty, new Uint8Array());
await assert.rejects(() => new SingleFileMessagePackBackingStore(empty).pullAll());
assert.equal((await readFile(empty)).length, 0);
// An empty array is the legacy envelope array with no envelopes: no header, no format claimed.
const legacyEmpty = join(dir, "legacy-empty.cc");
await writeFile(legacyEmpty, Uint8Array.of(0x90));
assert.deepEqual(await new SingleFileMessagePackBackingStore(legacyEmpty).pullAll(), []);
});
test("SingleFileMessagePackBackingStore reports a store it cannot reach as an I/O error", { skip: process.platform === "win32" }, async () => {
const dir = await mkdtemp(join(tmpdir(), "cultcache-unreachable-"));
const loop = join(dir, "loop.cc");
await symlink(loop, loop);
const parent = join(dir, "body");
await writeFile(parent, "not a directory");
for (const [path, code] of [[loop, "ELOOP"], [join(parent, "store.cc"), "ENOTDIR"]] as const) {
await assert.rejects(
() => new SingleFileMessagePackBackingStore(path).pullAll(),
(error: NodeJS.ErrnoException) => error.code === code,
);
}
});
test("SingleFileMessagePackBackingStore refusals never echo a value from the store", async () => {
const dir = await mkdtemp(join(tmpdir(), "cultcache-canary-"));
const file = join(dir, "store.cc");
const cases: Array<[unknown[], boolean | undefined]> = [
[["cultcache.store.v12", [], []], true],
[["cultcache.store.SECRET-HEADER", [], []], false],
[["cultcache.store.v12SECRET", [], []], false],
[["cultcache.store.v", [], []], false],
[["cultcache.store.v1", ["SECRET-CATALOG-TEXT"], []], undefined],
];
for (const [value, echoed] of cases) {
await writeFile(file, encode(value));
const header = value[0] as string;
await assert.rejects(
() => new SingleFileMessagePackBackingStore(file).pullAll(),
(error: Error) => {
if (echoed) {
return error.message.includes(`"${header}"`);
}
return !error.message.includes("SECRET")
&& (echoed === undefined || (!error.message.includes(`"${header}"`) && error.message.includes(`of ${header.length} bytes`)));
},
);
}
});
test("SingleFileMessagePackBackingStore refuses a dangling symlink at its path and writes nothing", { skip: process.platform === "win32" }, async () => {
const dir = await mkdtemp(join(tmpdir(), "cultcache-dangling-"));
const volume = join(dir, "volume");
await mkdir(volume);
const file = join(dir, "store.cc");
await symlink(join(volume, "store.cc"), file);
const store = new SingleFileMessagePackBackingStore(file);
const envelope = { key: "k", type: "t", payload: documentPayloadFromBytes(Uint8Array.of(0x90)), storedAt: "2026-09-30T00:00:00Z" };
await assert.rejects(() => store.pullAll(), (error: NodeJS.ErrnoException) => error.code === "ENOENT");
await assert.rejects(() => store.push(envelope), (error: NodeJS.ErrnoException) => error.code === "ENOENT");
await assert.rejects(() => store.pushAll([envelope], { soft: true }), (error: NodeJS.ErrnoException) => error.code === "ENOENT");
assert.ok((await lstat(file)).isSymbolicLink());
assert.deepEqual(await readdir(volume), []);
});
test("SingleFileMessagePackBackingStore record refusal names only a string key", async () => {
const dir = await mkdtemp(join(tmpdir(), "cultcache-record-key-"));
const file = join(dir, "store.cc");
const secret = new TextEncoder().encode("SECRET-BYTES");
for (const [key, schema] of [[["SECRET-IN-ARRAY"], "s"], [secret, "s"], ["k", { SECRET: 1 }]] as const) {
await writeFile(file, encode(["cultcache.store.v1", [], [[key, schema, "t", Uint8Array.of(0x90), "extra"]]]));
await assert.rejects(
() => new SingleFileMessagePackBackingStore(file).pullAll(),
(error: Error) => !error.message.includes("SECRET") && !error.message.includes("83,69,67") && error.message.includes("not a valid store"),
);
}
});
// What a write does through a symbolic link today. R3 decides whether a write resolves the link
// or refuses it; these pin the current behaviour so that change is deliberate.
test("R3 decides resolve-or-refuse: a push through a live link replaces the link", { skip: process.platform === "win32" }, async () => {
const dir = await mkdtemp(join(tmpdir(), "cultcache-live-link-"));
const volume = join(dir, "volume");
await mkdir(volume);
const target = join(volume, "store.cc");
const envelope = { key: "a", type: "t", payload: documentPayloadFromBytes(Uint8Array.of(0x90)), storedAt: "2026-09-30T00:00:00Z" };
await new SingleFileMessagePackBackingStore(target).push(envelope);
const before = await readFile(target);
const link = join(dir, "store.cc");
await symlink(target, link);
await new SingleFileMessagePackBackingStore(link).push({ ...envelope, key: "b" });
assert.ok((await lstat(link)).isFile());
assert.deepEqual(await readFile(target), before);
});
test("R3 decides resolve-or-refuse: a pushAll through a dangling link replaces the link", { skip: process.platform === "win32" }, async () => {
const dir = await mkdtemp(join(tmpdir(), "cultcache-dangling-push-all-"));
const volume = join(dir, "volume");
await mkdir(volume);
const link = join(dir, "store.cc");
await symlink(join(volume, "store.cc"), link);
await new SingleFileMessagePackBackingStore(link).pushAll([{ key: "a", type: "t", payload: documentPayloadFromBytes(Uint8Array.of(0x90)), storedAt: "2026-09-30T00:00:00Z" }]);
assert.ok((await lstat(link)).isFile());
assert.deepEqual(await readdir(volume), []);
});
test("CultCache inspector refuses the same vectors by name", async () => {
const inspect = async (name: string) => inspectCultCacheBytes(name, await readFile(join(variantVectors, name)));
await assert.rejects(() => inspect("unknown-header.msgpack"), /cultcache\.store\.v9/u);
await assert.rejects(
() => inspect("extra-slot-full-payload.msgpack"),
(error: Error) => error.message.includes("item:anvil") && error.message.includes(itemSchemaId),
);
});
test("a v1 store written at the base commit still reads byte for byte", async () => {
const envelopes = await pullVector("v1-base.msgpack");
assert.deepEqual(envelopes.map((entry) => [entry.key, entry.type]), [["alpha", "vectors.item"], ["beta", "vectors.item"]]);
assert.deepEqual([...documentPayloadBytes(envelopes[0]!.payload)], [0x92, 0xa5, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x01]);
assert.deepEqual([...documentPayloadBytes(envelopes[1]!.payload)], [0x92, 0xa4, 0x62, 0x65, 0x74, 0x61, 0x02]);
});
// ---------------------------------------------------------------------------------------------
// Cross-runtime store lock: each runtime's writer excludes the others on `<file>.lock`.
import { spawn } from "node:child_process";
import { withStoreWriteLock } from "../src/store-file";
type LockHolder = { exited: Promise<number | null>; stderr: () => string };
/** Starts a peer's `hold-lock` mode and resolves once it prints {"held":true}. */
async function holdLockWith(command: string, args: string[], cwd: string): Promise<LockHolder> {
const child = spawn(command, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
let errors = "";
child.stderr.on("data", (chunk) => {
errors += String(chunk);
});
const exited = new Promise<number | null>((resolveExit) => child.on("exit", (code) => resolveExit(code)));
await new Promise<void>((resolveHeld, rejectHeld) => {
let seen = "";
child.stdout.on("data", (chunk) => {
seen += String(chunk);
if (seen.includes('"held":true')) {
resolveHeld();
}
});
void exited.then((code) => rejectHeld(new Error(`hold-lock exited ${code} before holding: ${errors}`)));
});
return { exited, stderr: () => errors };
}
const lockHolders: Record<"csharp" | "rust", (file: string, ms: number) => Promise<LockHolder>> = {
csharp: (file, ms) =>
holdLockWith(dotnetCommand, [csharpInteropDll, "hold-lock", "--file", file, "--ms", String(ms)], cultLibRoot),
rust: (file, ms) =>
holdLockWith(rustInteropBinary, ["hold-lock", "--file", file, "--ms", String(ms)], cultcacheRsRoot),
};
const lockProbeEntry = (key: string): CultCacheEnvelope => ({
key,
type: "t",
payload: documentPayloadFromBytes(Uint8Array.of(0x90)),
storedAt: "2026-10-20T00:00:00Z",
});
for (const holder of ["csharp", "rust"] as const) {
test(`a TypeScript write waits while a ${holder === "csharp" ? "C#" : "Rust"} writer holds the store lock`, async () => {
await buildInteropPeers();
const dir = await mkdtemp(join(tmpdir(), "cultcache-lock-ts-waits-"));
const file = join(dir, "store.cc");
const held = await lockHolders[holder](file, 1500);
let finished = false;
const push = new SingleFileMessagePackBackingStore(file).push(lockProbeEntry("ts-record")).then(() => {
finished = true;
});
await new Promise((resolveWait) => setTimeout(resolveWait, 500));
assert.equal(finished, false, "the push must wait for the foreign lock");
assert.equal(existsSync(file), false, "nothing is written while the foreign writer holds the lock");
assert.equal(await held.exited, 0, held.stderr());
await push;
const keys = (await new SingleFileMessagePackBackingStore(file).pullAll()).map((entry) => entry.key);
assert.deepEqual(keys, ["ts-record"]);
});
}
for (const writer of ["csharp", "rust"] as const) {
test(`a ${writer === "csharp" ? "C#" : "Rust"} writer waits while a TypeScript writer holds the store lock`, async () => {
await buildInteropPeers();
const dir = await mkdtemp(join(tmpdir(), "cultcache-lock-peer-waits-"));
const file = join(dir, "store.cc");
const noteKey = `note:${writer}-writer`;
let peer: Promise<unknown> | undefined;
let peerDone = false;
await withStoreWriteLock(file, async () => {
peer =
writer === "csharp"
? execFileAsync(dotnetCommand, [csharpInteropDll, "write", "--file", file, "--runtime-id", "csharp-writer"], { cwd: cultLibRoot, timeout: 60_000 })
: execFileAsync(rustInteropBinary, ["write", "--file", file, "--runtime-id", "rust-writer"], { cwd: cultcacheRsRoot, timeout: 60_000 });
void peer.then(
() => (peerDone = true),
() => (peerDone = true),
);
await new Promise((resolveWait) => setTimeout(resolveWait, 1500));
assert.equal(peerDone, false, "the peer must still be waiting for the lock");
assert.equal(existsSync(file), false, `${noteKey} is absent while TypeScript holds the lock`);
});
await peer;
const keys = (await new SingleFileMessagePackBackingStore(file).pullAll()).map((entry) => entry.key);
assert.ok(keys.includes(noteKey), `${noteKey} is in the store after release`);
});
}
test("the store lock gives up on a Rust holder after its wait bound and names the lock path", async () => {
await buildInteropPeers();
const dir = await mkdtemp(join(tmpdir(), "cultcache-lock-timeout-"));
const file = join(dir, "store.cc");
const held = await lockHolders.rust(file, 2000);
await assert.rejects(
withStoreWriteLock(file, async () => undefined, { timeoutMs: 200 }),
(error: Error) => error.message.includes(`${file}.lock`),
);
assert.equal(await held.exited, 0, held.stderr());
});