CultNet TS: the registry resolves only ids a definition declares

Soul F1, F2 and F3 on the registry. The F2 principle (operator,
2026-09-30): "CultCache never destroys or relabels a record it doesn't
understand; keeps it byte-for-byte, refuses writes that would overwrite it."

- F1: the registry derives every binding's identity with cultcache-ts's
  schemaIdentityOf, so it stamps, refuses and resolves exactly as the cache
  does. P1 (A with schema name "p1.shared", B with schema id "p1.shared")
  is now refused by both.
- F2a: an id nobody owns resolves to the one binding that lists it as
  compatible. Several listers and no owner is a SchemaConflictError naming
  the id and the listers, whatever order they were registered in; an owner
  beats every lister. This is refused at resolution, as the cache refuses
  it at load, not at registration: refusing at registration would make the
  outcome depend on whether the owner was registered before the listers.
- F2b: version inference is deleted. "p3.note.v7" no longer resolves to
  the type named "p3.note" and gets relabelled "p3.note.v1".
- F2c: a schema version string, and a schema name, are not ids. Snapshot
  filters, typed puts and deletes, and raw puts match only declared ids.
- F3: a raw record under an id no binding declares is refused. The
  registry no longer tries each binding's parser and stores the record
  under the first that accepts it (P5: an identity parser registered first
  took everything).
- The dead resolvePayloadSchemaVersion helper is gone.

Tests: the three shape- and alias-routing tests are replaced by P1, P2,
P3/P4 and P5 as behaviour tests. The one-type-per-schema test registers two
bindings ahead of the holder (F6/M11).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-30 19:49:41 +02:00
commit 0ee98e8c2e
2 changed files with 180 additions and 246 deletions

View file

@ -7,6 +7,7 @@ import {
type CultCacheDocumentValue,
type CultCacheEnvelope,
SchemaConflictError,
schemaIdentityOf,
} from "@gamecult/cultcache-ts";
import {
@ -39,7 +40,9 @@ export function defineCultNetDocumentBinding<
export class CultNetDocumentRegistry {
readonly #bindings = new Map<string, CultNetDocumentBinding>();
readonly #schemaBindings = new Map<string, CultNetDocumentBinding>();
// The binding that owns each schema id, and every binding listing an id as compatible, in registration order.
readonly #owners = new Map<string, CultNetDocumentBinding>();
readonly #listers = new Map<string, CultNetDocumentBinding[]>();
constructor(bindings: Iterable<CultNetDocumentBinding> = []) {
for (const binding of bindings) {
@ -48,26 +51,32 @@ export class CultNetDocumentRegistry {
}
register(binding: CultNetDocumentBinding): this {
// One definition owns a type, a schema id and a schema name. Another definition claiming any of them is
// refused, so a schema never resolves to two types.
const schemaId = schemaIdForBinding(binding);
const schemaName = schemaNameForBinding(binding);
for (const holder of this.#uniqueBindings()) {
// One definition owns a type, a schema id and a schema name, derived as CultCache derives them. Another definition
// claiming any of them is refused, so a schema never resolves to two types.
const identity = schemaIdentityOf(binding.definition);
for (const holder of this.#bindings.values()) {
if (holder.definition === binding.definition) {
continue;
}
const held = schemaIdentityOf(holder.definition);
const claimed =
holder.definition.type === binding.definition.type ? `type "${binding.definition.type}"`
: schemaIdForBinding(holder) === schemaId ? `schema id "${schemaId}"`
: schemaNameForBinding(holder) === schemaName ? `schema name "${schemaName}"`
: held.schemaId === identity.schemaId ? `schema id "${identity.schemaId}"`
: held.schemaName === identity.schemaName ? `schema name "${identity.schemaName}"`
: undefined;
if (claimed) {
throw new SchemaConflictError(schemaId, [schemaNameForBinding(holder), schemaName], "",
`CultNet ${claimed} is already bound to type "${holder.definition.type}"; type "${binding.definition.type}" (schema id "${schemaId}") cannot also be bound.`);
throw new SchemaConflictError(identity.schemaId, [held.schemaName, identity.schemaName], "",
`CultNet ${claimed} is already bound to type "${holder.definition.type}"; type "${binding.definition.type}" (schema id "${identity.schemaId}") cannot also be bound.`);
}
}
this.#bindings.set(binding.definition.type, binding);
this.#schemaBindings.set(schemaIdForBinding(binding), binding);
this.#owners.set(identity.schemaId, binding);
for (const schemaId of identity.compatibleSchemaIds) {
const listers = this.#listers.get(schemaId) ?? [];
if (schemaId !== identity.schemaId && !listers.some(lister => lister.definition === binding.definition)) {
this.#listers.set(schemaId, [...listers, binding]);
}
}
return this;
}
@ -75,16 +84,22 @@ export class CultNetDocumentRegistry {
return this.#bindings.get(documentType);
}
// The binding a record under this schema id belongs to: the id's owner, else the one binding that lists it as
// compatible. An id nothing declares resolves to nothing. An id that several bindings list and none owns is refused,
// as the cache refuses it, whatever order the bindings were registered in.
getBySchemaId(schemaId: string): CultNetDocumentBinding | undefined {
const exact = this.#schemaBindings.get(schemaId);
if (exact) {
return exact;
const owner = this.#owners.get(schemaId);
if (owner) {
return owner;
}
const requestedSchemaIds = new Set([schemaId]);
return this.#uniqueBindings().find(binding =>
schemaMatchesBinding(schemaIdForBinding(binding), binding, requestedSchemaIds),
);
const listers = this.#listers.get(schemaId) ?? [];
if (listers.length > 1) {
const types = listers.map(lister => lister.definition.type).sort();
throw new SchemaConflictError(schemaId, listers.map(lister => schemaIdentityOf(lister.definition).schemaName).sort(), "",
`CultNet schema id "${schemaId}" is owned by no bound type and declared compatible by several (${types.join(", ")}), ` +
"so it resolves to none. Bind the type that owns the id, or declare it on one type.");
}
return listers[0];
}
createDocumentPutMessage<TDefinition extends AnyCultCacheDocumentDefinition>(
@ -186,7 +201,7 @@ export class CultNetDocumentRegistry {
const binding = this.#requireBinding(envelope.type);
const schemaId = schemaIdForEnvelope(envelope, binding);
if (requestedSchemaIds && !schemaMatchesBinding(schemaId, binding, requestedSchemaIds)) {
if (requestedSchemaIds && !answersTo(schemaId, binding, requestedSchemaIds)) {
continue;
}
@ -223,7 +238,7 @@ export class CultNetDocumentRegistry {
const binding = this.#requireBinding(envelope.type);
const schemaId = schemaIdForEnvelope(envelope, binding);
if (requestedSchemaIds && !schemaMatchesBinding(schemaId, binding, requestedSchemaIds)) {
if (requestedSchemaIds && !answersTo(schemaId, binding, requestedSchemaIds)) {
continue;
}
@ -265,7 +280,7 @@ export class CultNetDocumentRegistry {
cache: CultCache,
message: CultNetDocumentPutRawMessage,
): Promise<unknown> {
const binding = this.#resolveRawDocumentBinding(message.document);
const binding = this.#requireSchemaBinding(message.document.schemaId);
return cache.putEnvelope(binding.definition, {
key: message.document.recordKey,
type: binding.definition.type,
@ -319,33 +334,6 @@ export class CultNetDocumentRegistry {
return binding;
}
// routes a replicated raw record to a local binding; the store always persists the local schema id; this is not store-reader recovery.
#resolveRawDocumentBinding(document: CultNetRawDocumentRecord): CultNetDocumentBinding {
const exact = this.getBySchemaId(document.schemaId);
if (exact) {
return exact;
}
const payload = new Uint8Array(document.payload);
for (const binding of this.#uniqueBindings()) {
try {
decodeDocumentValue(binding.definition, payload);
return binding;
} catch {
// Keep looking; a foreign schema id is acceptable only when the payload validates locally.
}
}
throw new Error(`No CultNet document binding is registered for schema "${document.schemaId}".`);
}
#uniqueBindings(): CultNetDocumentBinding[] {
return [...new Set([
...this.#schemaBindings.values(),
...this.#bindings.values(),
])];
}
#createRawDocumentRecord(envelope: CultCacheEnvelope): CultNetRawDocumentRecord {
const binding = this.#requireBinding(envelope.type);
return {
@ -359,47 +347,17 @@ export class CultNetDocumentRegistry {
}
function schemaIdForBinding(binding: CultNetDocumentBinding): string {
return binding.definition.schemaId ?? binding.definition.type;
return schemaIdentityOf(binding.definition).schemaId;
}
function schemaNameForBinding(binding: CultNetDocumentBinding): string {
return binding.definition.schemaName ?? binding.definition.type;
}
function schemaMatchesBinding(
// A held record matches a snapshot filter when the filter names the id it carries or any id its binding answers to.
function answersTo(
schemaId: string,
binding: CultNetDocumentBinding,
requestedSchemaIds: Set<string>,
): boolean {
if (requestedSchemaIds.has(schemaId)) {
return true;
}
const definition = binding.definition;
if (definition.schemaId && requestedSchemaIds.has(definition.schemaId)) {
return true;
}
if (definition.schemaName && requestedSchemaIds.has(definition.schemaName)) {
return true;
}
if (definition.schemaVersion && requestedSchemaIds.has(definition.schemaVersion)) {
return true;
}
if (definition.compatibleSchemaIds?.some(candidate => requestedSchemaIds.has(candidate))) {
return true;
}
return Array.from(requestedSchemaIds)
.some(candidate => inferSchemaName(candidate) === definition.schemaName);
}
function inferSchemaName(schemaId: string): string | undefined {
const marker = schemaId.lastIndexOf(".v");
if (marker <= 0 || marker + 2 >= schemaId.length) {
return undefined;
}
const version = schemaId.slice(marker + 2);
return /^\d+$/u.test(version) ? schemaId.slice(0, marker) : undefined;
return requestedSchemaIds.has(schemaId)
|| schemaIdentityOf(binding.definition).compatibleSchemaIds.some(candidate => requestedSchemaIds.has(candidate));
}
function schemaIdForEnvelope(
@ -409,17 +367,6 @@ function schemaIdForEnvelope(
return envelope.schemaId ?? schemaIdForBinding(binding);
}
function resolvePayloadSchemaVersion<TDefinition extends AnyCultCacheDocumentDefinition>(
binding: CultNetDocumentBinding<TDefinition>,
value: CultCacheDocumentValue<TDefinition>,
): string | undefined {
if (typeof binding.payloadSchemaVersion === "function") {
return binding.payloadSchemaVersion(value);
}
return binding.payloadSchemaVersion;
}
function decodeDocumentValue<TDefinition extends CultCacheDocumentDefinition>(
definition: TDefinition,
payload: Uint8Array,

View file

@ -1589,57 +1589,145 @@ test("rudp sequence-neutral acknowledgements interoperate with ordered receivers
assert.deepEqual(receiver.receive(request).delivered.map(frame => Buffer.from(frame.payload).toString("utf8")), ["snapshot"]);
});
test("CultNet raw replication applies compatible foreign-schema snapshots", async () => {
const tempDir = mkdtempSync(join(tmpdir(), "cultnetts-foreign-schema-raw-"));
// A replicated record whose schema id no binding declares is refused, whatever its payload parses as: the registry never
// guesses a type from a payload's shape and never relabels a record it does not understand.
test("CultNet raw replication refuses a record under a schema id no binding declares, whatever its shape", async () => {
const note = defineDocumentType({
type: "p5.note", schemaId: "p5.note.v1", schemaName: "p5.note", schema: z.object({ body: z.string() }),
});
// Registered first, and accepts every payload: shape routing would store the record under it.
const anything = defineDocumentType({ type: "p5.anything", schema: z.any() });
const registry = new CultNetDocumentRegistry([
defineCultNetDocumentBinding({ definition: anything }),
defineCultNetDocumentBinding({ definition: note }),
]);
const origin = CultCache.builder().withDocumentType(note).build();
await origin.put(note, "k", { body: "from a peer" });
const snapshot = registry.createRawSnapshotResponse(origin, "p5");
assert.equal(snapshot.documents[0]?.schemaId, "p5.note.v1");
try {
const documentDefinition = defineDocumentType({
type: "cultnet.note",
schemaId: "cultnet.note.v1",
schemaName: "cultnet.note",
schemaVersion: "cultnet.note.v1",
schema: z.object({
schema_version: z.string(),
noteId: z.string(),
body: z.string(),
for (const schemaId of ["runtime.generated.p5.note.ui.99", "p5.note.v7", "p5.note"]) {
const target = CultCache.builder().withDocumentType(note).withDocumentType(anything).build();
await assert.rejects(
registry.applyRawSnapshotResponse(target, {
...snapshot,
documents: snapshot.documents.map(document => ({ ...document, schemaId })),
}),
name: "noteId",
});
const registry = new CultNetDocumentRegistry([
defineCultNetDocumentBinding({ definition: documentDefinition }),
]);
const originCache = CultCache.builder()
.withDocumentType(documentDefinition)
.withGenericStore(new SingleFileMessagePackBackingStore(join(tempDir, "origin.msgpack")))
.build();
const targetCache = CultCache.builder()
.withDocumentType(documentDefinition)
.withGenericStore(new SingleFileMessagePackBackingStore(join(tempDir, "target.msgpack")))
.build();
await originCache.put(documentDefinition, "note:foreign-schema", {
schema_version: "cultnet.note.v1",
noteId: "note:foreign-schema",
body: "accepted by payload shape",
});
const rawSnapshot = registry.createRawSnapshotResponse(originCache, "foreign-schema-raw");
rawSnapshot.documents[0]!.schemaId = "runtime.generated.cultnet.note.ui.99";
await registry.applyRawSnapshotResponse(targetCache, rawSnapshot);
assert.equal(
targetCache.getRequired(documentDefinition, "note:foreign-schema").body,
"accepted by payload shape",
(error: unknown) => error instanceof Error
&& error.message === `No CultNet document binding is registered for schema "${schemaId}".`,
);
assert.equal(
targetCache.getRequiredEnvelope(documentDefinition, "note:foreign-schema").schemaId,
"cultnet.note.v1",
);
} finally {
rmSync(tempDir, { recursive: true, force: true });
assert.deepEqual(target.snapshot(), [], `nothing was stored for ${schemaId}`);
}
});
// The registry stamps and resolves a definition's schema id exactly as CultCache does, so a record a cache wrote is read
// back as the type that wrote it on every peer.
test("CultNet registry derives a definition's schema id as the cache does, and refuses what the cache refuses", async () => {
const shape = z.object({ body: z.string() });
const a = defineDocumentType({ type: "p1.a", schemaName: "p1.shared", schema: shape });
const b = defineDocumentType({ type: "p1.b", schemaId: "p1.shared", schemaName: "p1.b", schema: shape });
const fillers = [0, 1].map(index => defineCultNetDocumentBinding({
definition: defineDocumentType({ type: `p1.filler${index}`, schema: shape }),
}));
// The cache refuses B beside A: A's records carry "p1.shared".
await assert.rejects(CultCache.builder().withDocumentType(a).build().registerDocumentType(b), SchemaConflictError);
for (const [first, second] of [[a, b], [b, a]]) {
// Two bindings stand ahead of the holder, so a check that looks only at the first holder misses it.
const registry = new CultNetDocumentRegistry([...fillers, defineCultNetDocumentBinding({ definition: first })]);
assert.throws(() => registry.register(defineCultNetDocumentBinding({ definition: second })), (error: unknown) => {
assert.ok(error instanceof SchemaConflictError, String(error));
assert.equal(error.schemaId, "p1.shared");
return true;
});
assert.equal(registry.get(second.type), undefined);
}
const registry = new CultNetDocumentRegistry([...fillers, defineCultNetDocumentBinding({ definition: a })]);
const origin = CultCache.builder().withDocumentType(a).build();
await origin.put(a, "k", { body: "written by a" });
const stamped = origin.getRequiredEnvelope(a, "k").schemaId;
assert.equal(stamped, "p1.shared");
assert.equal(registry.createDocumentPutMessage(registry.get("p1.a")!, "m", "k", { body: "x" }).document.schemaId, stamped);
assert.equal(registry.createRawDocumentPutMessage(registry.get("p1.a")!, "m", "k", { body: "x" }).document.schemaId, stamped);
assert.equal(registry.getBySchemaId(stamped!)?.definition, a);
const peer = CultCache.builder().withDocumentType(a).build();
await registry.applyRawSnapshotResponse(peer, registry.createRawSnapshotResponse(origin, "p1"));
assert.deepEqual(peer.getRequired(a, "k"), { body: "written by a" });
assert.equal(peer.getRequiredEnvelope(a, "k").schemaId, stamped);
});
// An id nobody owns resolves to the one binding that lists it as compatible. Several listers and no owner resolve to
// none, and the refusal does not depend on the order they were registered in. An owner beats every lister.
test("CultNet registry refuses an unowned schema id that several bindings list, in any order", async () => {
const shape = z.object({ body: z.string() });
const first = defineDocumentType({ type: "p2.first", schemaId: "p2.first", compatibleSchemaIds: ["p2.old"], schema: shape });
const second = defineDocumentType({ type: "p2.second", schemaId: "p2.second", compatibleSchemaIds: ["p2.old"], schema: shape });
const owner = defineDocumentType({ type: "p2.owner", schemaId: "p2.old", schema: shape });
const bind = (definition: typeof first) => defineCultNetDocumentBinding({ definition });
for (const order of [[first, second], [second, first]]) {
const registry = new CultNetDocumentRegistry(order.map(bind));
assert.throws(() => registry.getBySchemaId("p2.old"), (error: unknown) => {
assert.ok(error instanceof SchemaConflictError, String(error));
assert.equal(error.schemaId, "p2.old");
assert.deepEqual(error.schemaNames, ["p2.first", "p2.second"]);
assert.ok(error.message.includes("(p2.first, p2.second)"), error.message);
return true;
});
const cache = CultCache.builder().withDocumentType(first).withDocumentType(second).build();
const origin = CultCache.builder().withDocumentType(first).build();
await origin.put(first, "k", { body: "old" });
const document = { ...registry.createRawSnapshotResponse(origin, "p2").documents[0]!, schemaId: "p2.old" };
await assert.rejects(
registry.applyRawDocumentPutMessage(cache, { schemaVersion: "cultnet.document_put_raw.v0", messageId: "p2", document }),
SchemaConflictError,
);
assert.deepEqual(cache.snapshot(), []);
for (const withOwner of [[...order, owner], [owner, ...order]]) {
assert.equal(new CultNetDocumentRegistry(withOwner.map(bind)).getBySchemaId("p2.old")?.definition, owner);
}
}
assert.equal(new CultNetDocumentRegistry([bind(first)]).getBySchemaId("p2.old")?.definition, first);
});
// A schema id is only ever an id a definition declares. A version string, a schema name, or an id that differs from a
// declared one by its version suffix names nothing.
test("CultNet registry resolves only declared schema ids, never a version, a name or an inferred version", async () => {
const note = defineDocumentType({
type: "p3.note",
schemaId: "p3.note.v1",
schemaName: "p3.note",
schemaVersion: "p4.note.v3",
schema: z.object({ body: z.string() }),
});
const registry = new CultNetDocumentRegistry([defineCultNetDocumentBinding({ definition: note })]);
const cache = CultCache.builder().withDocumentType(note).build();
await cache.put(note, "k", { body: "held" });
assert.equal(registry.getBySchemaId("p3.note.v1")?.definition, note);
for (const undeclared of ["p3.note.v7", "p4.note.v3", "p3.note"]) {
assert.equal(registry.getBySchemaId(undeclared), undefined, undeclared);
const filter = { schemaVersion: "cultnet.snapshot_request.v0" as const, messageId: "f", schemaIds: [undeclared] };
assert.deepEqual(registry.createSnapshotResponse(cache, "f", filter).documents, [], undeclared);
assert.deepEqual(registry.createRawSnapshotResponse(cache, "f", filter).documents, [], undeclared);
await assert.rejects(registry.applyDocumentPutMessage(cache, {
schemaVersion: "cultnet.document_put.v0",
messageId: "put",
document: { schemaId: undeclared, recordKey: "k", storedAt: "2026-09-30T00:00:00.000Z", payload: { body: "relabelled" } },
}), /No CultNet document binding/);
await assert.rejects(registry.applyDocumentDeleteMessage(cache, {
schemaVersion: "cultnet.document_delete.v0", messageId: "delete", schemaId: undeclared, recordKey: "k",
}), /No CultNet document binding/);
assert.deepEqual(cache.getRequired(note, "k"), { body: "held" });
}
const filter = { schemaVersion: "cultnet.snapshot_request.v0" as const, messageId: "f", schemaIds: ["p3.note.v1"] };
assert.equal(registry.createSnapshotResponse(cache, "f", filter).documents.length, 1);
assert.equal(registry.createRawSnapshotResponse(cache, "f", filter).documents.length, 1);
});
test("CultNet document registry binds one type per schema and refuses a second, typed", () => {
const noteShape = z.object({ noteId: z.string() });
const owner = defineDocumentType({ type: "tests.note", schemaId: "id.note", schemaName: "tests.note", schema: noteShape });
@ -1656,7 +1744,13 @@ test("CultNet document registry binds one type per schema and refuses a second,
[sameType, "id.third", ["tests.note", "tests.third"]],
] as const) {
for (const order of [[owner, claimant], [claimant, owner]]) {
const registry = new CultNetDocumentRegistry([defineCultNetDocumentBinding({ definition: order[0] })]);
// Two bindings are registered ahead of the holder, so a conflict loop that checks only the first holder fails.
const registry = new CultNetDocumentRegistry([
...["tests.filler-a", "tests.filler-b"].map(type => defineCultNetDocumentBinding({
definition: defineDocumentType({ type, schema: noteShape }),
})),
defineCultNetDocumentBinding({ definition: order[0] }),
]);
assert.throws(() => registry.register(defineCultNetDocumentBinding({ definition: order[1] })), (error: unknown) => {
assert.ok(error instanceof SchemaConflictError, String(error));
const ownerFirst = order[0] === owner;
@ -1681,113 +1775,6 @@ test("CultNet document registry binds one type per schema and refuses a second,
assert.equal(registry.getBySchemaId("id.lister")?.definition, lister);
});
test("CultNet document registry filters snapshots by schema aliases", async () => {
const tempDir = mkdtempSync(join(tmpdir(), "cultnetts-alias-snapshot-"));
try {
const documentDefinition = defineDocumentType({
type: "cultnet.alias_note",
schemaId: "sha256:cultnet-alias-note",
schemaName: "cultnet.alias_note",
schemaVersion: "cultnet.alias_note.v1",
schema: z.object({
schema_version: z.string(),
noteId: z.string(),
body: z.string(),
}),
name: "noteId",
});
const registry = new CultNetDocumentRegistry([
defineCultNetDocumentBinding({ definition: documentDefinition }),
]);
const cache = CultCache.builder()
.withDocumentType(documentDefinition)
.withGenericStore(new SingleFileMessagePackBackingStore(join(tempDir, "alias.msgpack")))
.build();
await cache.put(documentDefinition, "note:alias", {
schema_version: "cultnet.alias_note.v1",
noteId: "note:alias",
body: "filtered by alias",
});
const typedSnapshot = registry.createSnapshotResponse(cache, "typed-alias", {
schemaVersion: "cultnet.snapshot_request.v0",
messageId: "typed-alias",
schemaIds: ["cultnet.alias_note.v1"],
});
const rawSnapshot = registry.createRawSnapshotResponse(cache, "raw-alias", {
schemaVersion: "cultnet.snapshot_request.v0",
messageId: "raw-alias",
schemaIds: ["cultnet.alias_note.v1"],
});
assert.equal(typedSnapshot.documents[0]?.schemaId, "sha256:cultnet-alias-note");
assert.equal(typedSnapshot.documents[0]?.recordKey, "note:alias");
assert.equal(rawSnapshot.documents[0]?.schemaId, "sha256:cultnet-alias-note");
assert.equal(rawSnapshot.documents[0]?.recordKey, "note:alias");
} finally {
rmSync(tempDir, { recursive: true, force: true });
}
});
test("CultNet document registry applies typed put and delete messages by schema alias", async () => {
const tempDir = mkdtempSync(join(tmpdir(), "cultnetts-alias-typed-"));
try {
const documentDefinition = defineDocumentType({
type: "cultnet.typed_alias_note",
schemaId: "sha256:cultnet-typed-alias-note",
schemaName: "cultnet.typed_alias_note",
schemaVersion: "cultnet.typed_alias_note.v1",
schema: z.object({
schema_version: z.string(),
noteId: z.string(),
body: z.string(),
}),
name: "noteId",
});
const registry = new CultNetDocumentRegistry([
defineCultNetDocumentBinding({ definition: documentDefinition }),
]);
const cache = CultCache.builder()
.withDocumentType(documentDefinition)
.withGenericStore(new SingleFileMessagePackBackingStore(join(tempDir, "typed-alias.msgpack")))
.build();
await registry.applyDocumentPutMessage(cache, {
schemaVersion: "cultnet.document_put.v0",
messageId: "typed-alias-put",
document: {
schemaId: "cultnet.typed_alias_note.v1",
recordKey: "note:typed-alias",
storedAt: "2026-06-27T00:00:00.000Z",
payload: {
schema_version: "cultnet.typed_alias_note.v1",
noteId: "note:typed-alias",
body: "typed alias applied",
},
},
});
assert.equal(
cache.getRequired(documentDefinition, "note:typed-alias").body,
"typed alias applied",
);
assert.equal(registry.getBySchemaId("cultnet.typed_alias_note.v1")?.definition.type, documentDefinition.type);
assert.equal(await registry.applyDocumentDeleteMessage(cache, {
schemaVersion: "cultnet.document_delete.v0",
messageId: "typed-alias-delete",
schemaId: "cultnet.typed_alias_note.v1",
recordKey: "note:typed-alias",
}), true);
assert.equal(cache.get(documentDefinition, "note:typed-alias"), undefined);
} finally {
rmSync(tempDir, { recursive: true, force: true });
}
});
test("CultNet interop slot compatibility defaults missing trailing fields and rejects mismatched slots", () => {
const note: InteropNote = {
schemaVersion: INTEROP_SCHEMA_VERSION,