diff --git a/src/GameCult.Caching/Contracts/cultcache-persistence-format.md b/src/GameCult.Caching/Contracts/cultcache-persistence-format.md index ece39387..79bc50e8 100644 --- a/src/GameCult.Caching/Contracts/cultcache-persistence-format.md +++ b/src/GameCult.Caching/Contracts/cultcache-persistence-format.md @@ -124,7 +124,16 @@ the payload's first field says. The catalog is the store's own description of it A writer keeps the same invariant: every record it writes carries a schema id that some catalog entry it writes publishes, as that entry's id or as one of its compatible ids. A cache stamps a -record with its registered schema's id, whatever id the record arrived under. +record with its registered schema's id, whatever id the record arrived under, when it loads the +record and when it puts one. The cache is the only owner of that stamp: a store writer never +changes the identity of a record it did not receive from its caller, so a record another writer +changed since this cache read it is written back as that writer left it. + +A cache resolves a record to a local type by the record's schema id: the registered type that owns +the id, else one that lists it as compatible. The name the catalog entry carries is metadata. It +names a local type only when no local type has the id, for a store written by a runtime whose +schema ids this one cannot know (a Rust type's schema id is its entry type). A schema renamed under +a stable id therefore opens in every runtime that holds the id. A reader resolves a record's schema id to the entry that owns it, that entry's own `schemaId`, and only when no entry owns the id to an entry that lists it as a compatible id (the first such entry @@ -138,9 +147,14 @@ own id, the descriptor wins: a rename or a stale content hash takes the descript members and compatible ids as they are. Entries are written as chosen, deduplicated by own id, with no union of compatible ids and no dependence on the order records arrive in; an entry no record needs is not written. A write is refused, and the store left as it was, when two entries of one tier share -an own id and disagree on the schema name, when records of different types share a schema id, or when -a record's id is published by no chosen entry. Each runtime refuses with one typed error naming the -id, the schema names and a record key: `CultSchemaConflictException` (C#), `SchemaConflictError` +an own id and disagree on the schema name, when records of different types share a schema id, when a +write would change which type an existing record resolves to (the schema names of the records already +in the store and of the records written differ), or when a record's id is published by no chosen entry. +The retyping refusal has one exception, a rename: when the entries of both the existing records and +the written ones own the id, the registered descriptor wins. (A C# cache stamps a record with its +descriptor's own id, so it cannot write a record under an id its descriptor only lists, and the +retyping refusal cannot arise there.) Each runtime refuses with one typed error naming the id, the +schema names and a record key: `CultSchemaConflictException` (C#), `SchemaConflictError` (Rust, TypeScript, Python). This is not decorative paperwork. It is what allows another CultCache