old-reader-refusal.sh gains gate <runtime> and per-runtime old revisions (b8fc5eb0 = cultlib-unity-v2.0.1,
02cf04b5 = cultcache-rs-v0.3.1, 45c2f400 = cultlib-unity-v1.0.60). C2aGateWriter writes the live v3 and v5 stores.
OldReaderGateProbe replaces OldReaderV3Probe. The vectors themselves follow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Absent writes, a store it reads is left byte for byte, and anything the reader refuses (an unreadable file, a dangling link) is refused. The readability manifest walk now runs a soft flush over every vector, so TypeScript and Rust are pinned by the same files.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The manifest held only three-byte and whole-store trailers, so a Rust reader that
tolerated a single trailing byte passed every suite while C#, TypeScript and Python
refused the file.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
store_header is the only reader of a store file's leading bytes. It returns the header slot (a MessagePack str only, never bin, as in C#, TS and Python) and separately the proof that the bytes are one whole array, so a newer store cut short after its header is still named unsupported. leading_header, read_array_header and read_string are deleted. decode_store_file takes the header the reader already found.
Vectors bin-header-v1/v3/v9 join the readability manifest as refuses x4. Tests: a rename fault step (WriteStep::Rename) shows a failed rename is NotReplaced, the store intact and no staging file left; every merging writer refuses through a dangling link; the unsupported-format refusal names v1 and v3 and echoes only ASCII-digit headers.
Not yet compiled or run at this commit.
Co-Authored-By: Claude Opus 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>
cultnet-py's resolve_document_and_schema_id_for_raw_record is replaced by resolve_document_for_raw_record, which resolves the
document only; cultmesh-py keys previous values by the local schema id, so a snapshot under a compatible id or the schema name
reports an update with its previous value. Writers (C#, TypeScript, Python) merge same-id entries only when their schema names match;
another schema's entry is dropped or the record gets a default entry under its own id, and a record's own id is claimed before an
entry that lists it as compatible. The C# single-file writer prefers the entry being written now, as the directory writer does, so
name, content hash and members come from the registered schema and the compatible ids are the union. Vector:
compatible-before-owner-v3. A cultnet-py test's schema path no longer depends on the working directory.
Co-Authored-By: Claude Sonnet 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>
The persistence contract requires the catalog to publish every schema its records name and specifies no recovery, so the catalog
is the store's own description: Rust, TypeScript and Python stop inferring a schema name from a payload's first field, as C#
never did, and a store naming an unpublished schema is unreadable in all four. missing-schema-versioned-v3 pins it with payloads
that open with a schema version.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
variant-slot-v3 overrides the variant's name, which the reader requires of a variant, so C# reads it and the others refuse the
fifth slot. The legacy-layout catalog vectors carry an entry per schema, so a store the reader accepts differs from the one it
refuses only by the bytes after it. missing-schema-v3 is refused by C# too.
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>
Rust's read path let a legacy envelope array with trailing bytes through when the
whole-array check was loosened, and no vector caught it. Add the vector; every
runtime refuses it.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A rewrite replaces a file only when the runtime's own reader opens it. C#
drops ReadStoreHeader (a second, header-only verdict that let a flush replace
files the reader refuses) and asks DeserializeSnapshot from open, flush and
commit; the reader now requires exactly one array, so C# refuses bytes after
the store on read as TS and Python do. A zero-byte file is an empty store for
all three paths. CultMesh.WriteSingleFileDocument reads the file it replaces
through the Mesh reader before writing. Rust reads and rewrites through one
classification (trailing bytes refused on read; a non-store first slot must be a
legacy envelope array the reader accepts). TypeScript checks a legacy array with
pullAll's own decoder before replacing it. Python already conformed.
Shared byte vectors and per-runtime verdicts live in
tests/vectors/document-variants-c2a/readability; every runtime's test reads
them. Holds gains second-dictionary-value and second-list-item cases. The
persistence format contract now marks v3 by content and states the readability
rule.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
old-reader-refusal.sh stages the C0 merge (e382bb4, or OLD_READER_REV) plus the v3 vector
and one probe per runtime onto a commit; the probe run from that tree is the old reader.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A store that can hold element ids declares cultcache.store.v3. These runtimes pass payload
bytes through, so they read it and write back the header they found; they do not decide
when a document carries ids. Vector: tests/vectors/document-variants-c2a/v3-base.msgpack.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>