SerializePayload takes the declared type. A call attaches its registry under the store's gate and restores the previous one, so no call leaves a registry on the store. Not yet run; the next commit set is verified on Yggdrasil.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
DirectoryMessagePackBackingStore judges the parsed snapshot's stamp after the stamp-first read (an array with no stamp parses to v1 and was opened as a store). The NewerStampOverUnreadableBody rows assert the fragment each refusal names, plus a binary stamp over a valid body. DescribeHeader gets its suffix, digit-bound and zero-digit cases. Owns findings empty-array-directory-manifest-opens, stamp-first-unpinned-at-store-paths, describe-header-suffix-unpinned.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Stamp rule runs on slot 0 before the body is scanned (JudgeStamp). DescribeHeader names a v<N> header that carries a suffix. Rust store_header walks on the grown stack. Drift tests declare Wide's id compatible (ownership by declaration). Mesh write-guard tests expect the typed refusals.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Identity: C2a's StoredSchemaId/StoredVersion owns; main's PersistedSchemaId, PersistedIdentity and both MarkWritten are deleted. Stamp rule: ReadStore judges the stamp before the body. Flush over damage refuses (merge-r1-precedence). Mesh single-file writes route through SingleFileMessagePackBackingStore. TS: store-file.ts commits, C2a's header handling kept. Rust: C2a's readable_store_format in front of the grown decoder. Build not yet verified at this commit.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Single-file PushAll reads the format stamp under the lock and refuses cultcache.store.v<N> with N > 2;
the directory store throws NotSupportedException for N > 4 from the manifest parse every write already runs.
An unreadable stamp keeps today's behaviour.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
R1's rules win where both sides decided the same question: a zero-byte file is
not a store (refused as unreadable in every runtime), only nothing at the path is
empty, a dangling link is an I/O error, refusals never echo stored values and echo
a header only as cultcache.store.v<digits>, writes report Rejected, NotReplaced
and ReplacedNotDurable. C2a's rules win on what it added: the v3 element-id
header, variants, whole-file proof, typed unreadable errors in C#, TypeScript and
Python. Rust keeps R1's CultCacheStoreUnreadable{path, kind}; C2a's
StoreUnreadableError is deleted. The readability manifest's zero-byte row is
refuses x4.
Not yet built or tested: verification follows in the next commits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RequireSingleFileFormat echoed any header string. It now names one only in
the shape cultcache.store.v<digits> and otherwise gives its byte length.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pins the C# side of the cross-runtime rule the Rust, Python and TS runtimes
now share. C# already refused a zero-byte file (the snapshot reader runs out
of bytes at the array header); this test keeps it that way. Its exception is
still EndOfStreamException, an IOException: that overlap belongs to R3's C#
work.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Entries that share a schema id are one schema seen by different writers, so the writer lists every id any of them lists, in any
order: C# single-file and directory (CultSchemaCatalogEntry.MergeById), TypeScript and Python. Rust synthesises one entry per id and
has no such merge. An id that one entry owns and another lists as compatible names the owner in Rust, TypeScript, Python and the TS
inspector, as C# already did (own-id-over-compatible-v3). Also: a directory manifest in an unreadable format is the typed refusal
naming it, cultnet-py applies and reports a record under the local schema id, and Rust's error prints its cause once.
Vectors for the Mesh layout discriminator: a nil and an integer at catalog slot 5.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A record whose schema id the catalog lists only as a compatible id now opens (the cache resolved a persisted schema by entry id
alone). CultMesh's fallback to the older catalog-entry reader fires only for a store in that layout, decided by the entry's slot 5,
not for any decode failure, so a current-layout store with a malformed slot is refused as CultCache refuses it.
CultStoreUnreadableException.Path names the refused file. New vectors: compatible-id-only-v3 (accepted by all four) and three
current-layout catalogs with one malformed slot. The contract states the writer's invariant.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
CultStoreUnreadableException (a NotSupportedException) is the one refusal for a store file this runtime cannot read, thrown
by the single-file reader, the rewriting paths and CultMesh's reads, with the cause as the inner exception. The reader is exactly
one array of three slots (or an empty array): a header alone and an extra top-level slot are refused instead of read as empty or
skipped. CultMesh's older-catalog fallback is the same reader with the older entry reader, so it also requires one array.
Vectors: bad-body, header-only, missing-schema, variant-slot, extra-top-slot in the readability set, and a legacy-catalog store
with and without bytes after it for CultMesh. The persistence contract says what is true.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
VariantTests drive the cache batch API and the real single-file store. The C1
vector was written by the code under test through that path; C# reads it and
rewrites it byte for byte, and the other runtimes' tests read the same bytes next.
The C0 tests that expected C# to refuse a v2 store now expect what C1 does with
it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The legacy catalog layout reaches CultMesh's fallback reader, and a nil header
was the only defaulted header; each gets one vector or test. The C# store
tests catch the refusal where the store attaches.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Delete the trailing Skip loop in the record reader, the ?? v1 header default,
and the mesh reader's copy of the record decoder. The single-file store and
CultMesh single-file reads accept cultcache.store.v1 only. Persistence-format
and parity docs state the rule.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Cut 3 of the CultCache migration (Aetheria docs/cultcache-migration-cut.md, 2.1
and 2.2). One commit, because routing, the batch that replaces the ambient
transaction, and conditional commit share one admission path.
Deleted first:
- globals: InitializeGlobals, MaterializeMissingGlobals, the two-argument
constructor, the initializeGlobals plumbing in CultCacheMessagePack,
ContainsDurableRecord (cache and directory store)
- replication: the push-to-every-store loops and the attach-time push of
existing records
- typeof(T) as the schema owner in AddAsync/UpsertAsync and in
CultNetDatabase.PutAsync/PutPredictedAsync
- the ambient transaction: ExecuteTransactionAsync x2, CultCacheTransaction,
CommitTransaction, VisibleStoredDocuments, the AsyncLocal, the SemaphoreSlim
gate, and every ambient check
- CultCacheOpenOptions.PullOnOpen; OpenAsync is Task.FromResult(Create(...))
- DirectoryMessagePackBackingStore.ResolveLegacyUncataloguedRecordCatalog: the
catalog precommit (fe55bff) is an ancestor of the v4 page format (eea8248),
so no v4 manifest can reach it
- CultCache.DeleteAsync<T>; its one caller, CultNetDatabaseSubscriptionClient,
called it by reflection and now calls Remove(key)
Added:
- AddBackingStore(store, params Type[] homes): attach validates (clean, not
already attached, one untyped store, no claimed home type, no admitted
record changes home) and then pulls under the gate
- Home(type): most derived routed home, else the untyped store
- Admit: every add and remove, from a write, a batch or a load, validates
(home, read-only, loaded-from-home, global singleton), lands durably, then
applies and publishes; a pull hands its whole delta over in one call, so a
refused record leaves the store and the cache unchanged
- read-only stores (CacheBackingStore(readOnly), both store constructors,
CultCacheOpenOptions.ReadOnly); flush skips them and writes only dirty stores
- assignable GetGlobal<T>/GetByName<T>/GetByIndex<T>/Watch<T>; more than one
candidate throws naming them
- Commit(Action<CultCacheBatch>) returning bool, TryCommit returning
CultCommitOutcome, CultCacheBatch (Upsert, Remove, Expect, ExpectUnchanged,
sealed after commit), CultCommitRequest; CacheBackingStore.CommitBatch
evaluates conditions under the store's exclusive lock
- single-file store: <path>.lock sidecar for CommitBatch and PushAll; a clean
store commits onto the file as it is, so records it never loaded survive
- storedAt minted in one place and bumped a tick when not later than the record
it replaces
Consumer adjustments in this repo: Mesh streaming tests drop PullOnOpen; the
Studio bridge stops reflecting on it; BackingStoreTests' global test creates
its global explicitly and its transaction tests are rewritten against Commit
(the two that proved ambient hydration rules are deleted); the pre-Cut 2
refusal test expects the attach to throw.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fixtures were produced by CultLib 0db1fe5 from a temporary worktree: a v4
directory store and a v1 single-file store holding the same three records.
Tests reopen both and assert every value, and assert the directory store
refuses v1, v1.directory, v2 and v3 manifests without touching the bytes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>