From bc6d524bdfaafaf152d4195774983f1b51efd430 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 22 Oct 2026 23:57:38 +0200 Subject: [PATCH] Contracts: cacheless single-file access; never reuse a dropped slot Store-composition gains the Cacheless single-file access section; schema-compatibility states the slot-reuse consequence per variants:ruling:slot-reuse-documented. Owns follow_up slot-reuse-contract-doc. Co-Authored-By: Claude Sonnet 5.5 --- .../cultcache-schema-compatibility.md | 6 +++++ .../Contracts/cultcache-store-composition.md | 22 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/GameCult.Caching/Contracts/cultcache-schema-compatibility.md b/src/GameCult.Caching/Contracts/cultcache-schema-compatibility.md index ad0855d9..dc7e7bea 100644 --- a/src/GameCult.Caching/Contracts/cultcache-schema-compatibility.md +++ b/src/GameCult.Caching/Contracts/cultcache-schema-compatibility.md @@ -61,6 +61,12 @@ either way is refused: a get-only property, a readonly field without `AllowPrivate`, or a property whose setter is non-public without `AllowPrivate`, when the constructor does not fill it. +Never reuse a dropped slot; retire it. A write set copies a record the writer did not touch forward as stored bytes, so an +untouched record keeps a dropped member's value in its slot through every write. A type that later puts a new member in that slot +reads the dropped value as its own. The store does not remember retired slots, and a reuse that changes the slot's shape is refused +at load, but a reuse of the same shape reads silently. This is the rule protobuf, Thrift and MessagePack-CSharp give for numbered +fields: leave the slot empty, and take the next unused number for a new member. + ## Which schema a record carries A record is written under the schema of its runtime type. Writing a derived diff --git a/src/GameCult.Caching/Contracts/cultcache-store-composition.md b/src/GameCult.Caching/Contracts/cultcache-store-composition.md index 5ee8bb68..bb67855c 100644 --- a/src/GameCult.Caching/Contracts/cultcache-store-composition.md +++ b/src/GameCult.Caching/Contracts/cultcache-store-composition.md @@ -347,6 +347,28 @@ type name is the CLR full name, the same rule as `System.Int32`. A reader in another runtime needs only the component count and primitive type. CultMath has no serialization; a CultMath encoding, if added, uses this shape. +## Cacheless single-file access + +A single-file store file can be read and written without a cache, through two public members of `SingleFileBackingStore` (for +example `SingleFileMessagePackBackingStore`): `ReadStoreSnapshot()` and `WriteRecord(record, catalogEntry, holdsIds, registry)`. + +- `ReadStoreSnapshot()` reads the file once, under the store's gate, and returns the durable snapshot (records, schema catalog, + format stamp), or null when nothing is at the path. It throws `CultStoreUnreadableException` for a file the reader refuses. +- `WriteRecord` writes one record under the store's lock: the record replaces the one at its key, every other record and catalog + entry is copied forward as stored, and the file is replaced atomically. + +They share everything with a cache's store and nothing is re-implemented beside it: one reader (the stamp is judged before the +body, and a file the reader refuses is refused by a read and by a write alike), the store's lock, the atomic replace, and the +header rule (`holdsIds` true marks a v3 store; false leaves it as the rule decides; null, for a payload the caller cannot read, +keeps whatever the file already says). Ownership by declaration binds a cacheless write too: a record at the key under a schema id +the written catalog entry does not own or list as compatible is refused with `CultSchemaConflictException`, and the file is left as +it was. A typed writer plans element ids with `CultElementIds.Plan` before it serializes and passes `CultElementIds.Holds` as +`holdsIds`. + +A store attached to a cache refuses `WriteRecord` with `InvalidOperationException`: write through the cache, whose staged writes +land through the same private write. CultMesh's single-file document helpers (`CultMesh.WriteSingleFileDocument`, +`ReadSingleFileDocument` and their payload forms) are built on these members and nothing else of the store. + ## Runtime status | Rule | C# | Rust | TypeScript | Python |