Contract: the cache owns the schema-id stamp, records resolve by schema id, writes may not retype

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-30 11:04:16 +02:00
commit 86dc2e33d0

View file

@ -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