CultCache TS: one function derives a definition's schema identity

Soul F1 (hold on 98999997..6e47710e): the cache stamped records with
schemaId ?? schemaName ?? type, while the CultNet registry and CultMesh's
schema descriptor used schemaId ?? type. A definition that set only a
schema name was stamped one id by the cache and bound under another by the
registry, so its records could land on a peer as a different type.

- schemaIdentityOf(definition) returns the schema id, schema name, schema
  version and every id the definition answers to (its own first). It is
  exported, and it is the only place that derivation lives.
- The cache's catalog entry is built from it; nothing else changes here.

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 426c956a40
3 changed files with 25 additions and 8 deletions

View file

@ -1,5 +1,6 @@
import { decode, encode } from "@msgpack/msgpack";
import { schemaIdentityOf } from "./document";
import { SchemaConflictError } from "./store-format";
import type {
AnyCultCacheDocumentDefinition,
@ -706,8 +707,7 @@ export class CultCache {
}
#createCatalogEntry(definition: AnyCultCacheDocumentDefinition): CultCacheSchemaCatalogEntry {
const schemaName = definition.schemaName ?? definition.type;
const schemaVersion = definition.schemaVersion ?? `${schemaName}.v1`;
const { schemaId, schemaName, schemaVersion, compatibleSchemaIds } = schemaIdentityOf(definition);
const canonicalSchemaJson = definition.canonicalSchemaJson ?? JSON.stringify({
schemaName,
schemaVersion,
@ -724,10 +724,6 @@ export class CultCache {
isName: member.isName === true,
})),
});
const schemaId = definition.schemaId ?? schemaName;
const compatibleSchemaIds = [
...new Set([schemaId, ...(definition.compatibleSchemaIds ?? [])]),
];
return {
schemaId,
@ -735,7 +731,7 @@ export class CultCache {
schemaVersion,
contentHash: definition.contentHash ?? schemaId,
canonicalSchemaJson,
compatibleSchemaIds,
compatibleSchemaIds: [...compatibleSchemaIds],
members: [...(definition.members ?? [])]
.sort((left, right) => left.slot - right.slot)
.map((member) => ({

View file

@ -69,6 +69,27 @@ export function defineDocumentType<TSchema extends CultCacheSchema>(
return Object.freeze({ ...definition });
}
// The identity a definition's records carry: the schema id they are stamped with, the schema name and version, and every id
// the definition answers to (its own first). Every CultCache runtime surface and every CultNet or CultMesh layer over it
// derives a definition's identity here and nowhere else, so a record's id means the same type wherever it is read.
export interface CultCacheSchemaIdentity {
readonly schemaId: string;
readonly schemaName: string;
readonly schemaVersion: string;
readonly compatibleSchemaIds: readonly string[];
}
export function schemaIdentityOf(definition: AnyCultCacheDocumentDefinition): CultCacheSchemaIdentity {
const schemaName = definition.schemaName ?? definition.type;
const schemaId = definition.schemaId ?? schemaName;
return {
schemaId,
schemaName,
schemaVersion: definition.schemaVersion ?? `${schemaName}.v1`,
compatibleSchemaIds: [...new Set([schemaId, ...(definition.compatibleSchemaIds ?? [])])],
};
}
export function defineDocumentRegistry<
TDefinitions extends readonly AnyCultCacheDocumentDefinition[],
>(...definitions: TDefinitions): CultCacheDocumentRegistry<TDefinitions> {

View file

@ -1,6 +1,6 @@
export { CultCache, CultCacheBuilder } from "./cult-cache";
export { inspectCultCacheBytes } from "./cult-cache-inspector";
export { defineDocumentRegistry, defineDocumentType } from "./document";
export { defineDocumentRegistry, defineDocumentType, schemaIdentityOf, type CultCacheSchemaIdentity } from "./document";
export { SingleFileMessagePackBackingStore } from "./single-file-messagepack-backing-store";
export { SchemaConflictError, StoreUnreadableError } from "./store-format";
export type {