Commit graph CultLib/packages/cultcache-rs/src
Author SHA1 Message Date
Meta Vens
f54472e5fe Merge origin/main (cultcache-rs 0.3.1, the whole-store writer fix) into the C2a landing branch
The stamp functions (Rust refuse_unread_format, TS refuseUnreadFormat) are the single owner of the stamp rule, accept v1 and v3, and are called only from the one reader every whole-store write runs under the lock. The fix's header peek (TS refuseUnreadStoreBytes, peekHeader) is deleted. Its tests that v3 is refused and that a stampless file is healed are dropped; the reversal is a Breaking line in the Rust and TS CHANGELOGs. The 0xff-after-stamp test now runs over v1 and v3.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 00:44:59 +02:00
Meta Vens
6c9e4aa509 cultcache-rs: one grown decoder setup for every decode of stored bytes
store_header and decode_grown both run through grown(); the copied maybe_grow and red-zone setup is deleted. Owns finding store-header-second-grown-decoder.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 23:53:11 +02:00
Meta Vens
6809e74137 cultcache-rs 0.3.1: push_all reads the store stamp under the lock and refuses a format it does not read
One owner of the stamp rule, refuse_unread_format, serves read_all_unlocked and push_all. A file push_all cannot read is written as before. Builds.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 22:26:00 +02:00
Meta Vens
4adcc21674 cultcache-rs, cultcache-ts tests: a whole-store write refuses a format it does not read (red)
Both new tests fail at this commit; it builds. Vector copied byte-identical from C2a (blob 9fc9b64f).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 22:22:05 +02:00
Meta Vens
c14b2c65d1 Land fixes: stamp judged before the body, deep stores on the grown stack, ported tests refuse by their typed errors
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>
2026-10-22 20:49:43 +02:00
Meta Vens
f2b3569822 Merge main at cultlib-unity-v2.0.0 (42dce361) into C2a: one owner per collision
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>
2026-10-22 20:35:33 +02:00
Meta Vens
c0a2b890f6 cultcache-rs: the decode door names a missing or duplicated field and never quotes a value
describe_decode_error keeps serde's own value-free texts for missing and
duplicate fields (static names from the type), maps unknown-variant and
invalid-type to kind descriptions, and leaves everything else opaque.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 04:22:22 +02:00
Meta Vens
9d2b64bd88 cultcache-rs docs: Windows line names the revision of the run it describes
The decode_stack Windows run is at 10a39dac (tests unchanged since 4b55c7d3);
the head after this commit awaits a Soul rerun.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 21:19:19 +02:00
Meta Vens
10a39dac98 cultcache-rs docs: debug floor stated for a plain caller; first-frame sentences removed; Windows line names its run
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 21:19:13 +02:00
Meta Vens
dbafbf52bc cultcache-rs docs: state the decoder's fixed stack floor, record the Windows run
The README and decode_payload claimed the calling thread needs only its own
frames. Measured, the door has a payload-independent floor (release 56 KiB vs
16 for a same-signature non-decoding function; debug 104 vs 88). Say so, and
replace "Windows not yet verified" with the one by-hand Starfire run of
decode_stack on x86_64-pc-windows-msvc.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 20:57:58 +02:00
Meta Vens
57243090ec MAX_RECORD_INLINE_BYTES doctests: serde-able fixtures, positive twins of each refusal
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 20:02:52 +02:00
Meta Vens
f7c0902d8d decode_stack tests: box the decoded result on the small thread, measured thread sizes; doctest fixture serde-able
Measured on Yggdrasil: the decode needs 56 KiB (Wide32) and 80 KiB (Largest) in
debug on a thread that returns a bool; returning the record through
std::thread's own frames adds several copies of it, so the tests box it and
size threads at 96 KiB and from 160 KiB.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 19:56:57 +02:00
Meta Vens
fc630d3b85 cultcache-rs 0.3.0: one grown decoder for stored bytes, derives refuse records over 16 KiB
decode_grown checks the stack before any frame that holds T, then serde_stacker
at every container; decode_payload walks the nesting bound and calls it; the
legacy envelope array, redb reads and v1 snapshots decode through it too.
Red zone 512 KiB against MAX_RECORD_INLINE_BYTES = 16 KiB, which the
DatabaseEntry, CultSlots and CultUnion derives assert at compile time.
cultcache-rs-derive 0.2.0.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 19:48:57 +02:00
Meta Vens
3b83cb6c08 Merge origin/main into hands/variants-c2a-main (main-into-c2a)
Sixteen conflicted files resolved by the spec's rule: main owns payload
ownership, bounds and the Clock seam; C2a owns persistence semantics and exact
binding. MintStoredAt is internal static (previous, now); the cache and the
store pass their cache's Clock. Package versions are main's. The cultmesh-ts
payload-parse acceptance and its test are deleted under retire-schema-alias;
peer snapshot reads take the schema's own id, else an id the definition lists
as compatible. The held firstRecordAtKey fallback is removed: main's handles
take definitions only, so nothing could reach it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 04:06:41 +02:00
Meta Vens
d3f8dea75a cultcache-rs: decode_payload grows its stack through serde_stacker
A payload inside the 256 nesting bound no longer depends on the caller's
thread stack: the rmp-serde deserializer runs under serde_stacker with a
256 KiB red zone and 2 MiB segments. Adds tests/decode_stack.rs (child
process per test, sized decode thread) and renames the 2 MiB union_parity
stack test to a 1 MiB pin.

Not yet built or run at this commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 02:38:23 +02:00
Meta Vens
a5c0f876de cultcache-rs: the payload nesting bound at put_raw_envelope and put_prepared_batch
Both raw write doors run the one check_payload_depth that decode_payload runs first, before any
store is touched; decode_payload is public so envelope readers elsewhere decode through the same
helper. Tests: union arms, maps and arrays at 256 and 257 through both doors, a batch that stages a
valid entry beside a deep one persists neither, the read vectors enter by a store file written
behind the cache.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 17:52:24 +02:00
Meta Vens
f06cd84596 cultcache-rs: element_header owns truncation; the walk tests fill skipped bytes with array markers
Mutation survivors of the first run: the unwrap_or(usize::MAX - k) fallbacks were equivalent
mutants, so a truncated header now returns None through the one helper. Child counts and long
lengths (map16/32, array16/32, str/bin/ext 16 and 32 bit) are tested with skipped bytes that
read as containers when a skip stops short. Builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 10:30:10 +02:00
Meta Vens
ca6671aefb cultcache-rs: payload nesting bound through one decode, and the derive findings deferred to this cut
check_payload_depth walks MessagePack headers iteratively and refuses the first container past
MAX_PAYLOAD_DEPTH (256); decode_payload is the only payload decode (get, get_all,
get_all_with_keys, finish_prepared_entry, envelope validation), so write is refused through
finish_prepared_entry. Derive: nil arm slots refused naming union and key, ext arm keys classed
non-integer, generated generics and local items prefixed (types named D, S, A, E, ArmKey now
derive), slot keys capped at 65535 at definition, SlotRecord requires a hidden method only the
derives emit, DatabaseEntry/CultSlots expansions are functions the type/schema/generics refusals
are unit-tested through. Tests follow in the next commit; this commit builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 10:13:50 +02:00
Meta Vens
c586640e81 cultcache-rs-derive: arm keys are Int32 and given once, an arm is a slots record, refusals classified like the reference
Key attributes (variant, field, entry type/schema) are refused when repeated. A CultUnion key
parses as the reference's Int32, negatives included; a key outside it is a compile error. Decode
classifies the key as the reference does: non-integer, outside Int32, undeclared (named), and a
pair of the wrong length is refused before its key is read; no value is echoed beyond the
undeclared key the reference names. The SlotRecord trait, implemented by the shared slot
generator, bounds an arm payload, so a String or Option arm is a compile error. compile_fail
fixtures use keys other than 0; the derive crate's unit tests assert each refusal's message.

Unverified: committed before the first build; the verification job runs next.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 04:37:47 +02:00
Meta Vens
e72f17421e CultSlots compile-fail docs: a field without a key, a generic struct
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 04:05:22 +02:00
Meta Vens
2d840d5ffb cultcache-rs: CultSlots and CultUnion derives writing the reference [key, armSlots] shape
One slot generator now serves DatabaseEntry and CultSlots. CultUnion writes
[key, slots] with unit arms as [key, []] and refuses unknown keys naming the
enum and declared keys. Tests hold the runtime to tests/vectors/union-parity.

Not yet built or run: this commit may not build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 03:50:30 +02:00
Meta Vens
12805eb32f CultCache Rust: a soft push_all asks the one reader, as TypeScript's does
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>
2026-10-03 01:28:47 +02:00
Meta Vens
da2b9482e5 Rust: the rename fault fires inside replace_file_atomically, at the call a swallowed error would hide
The seam sat before the call, so a mutant that swallowed the real rename's result was invisible to it. It now makes the rename itself fail, on both platforms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 21:11:07 +02:00
Meta Vens
9a251fe60c Rust: one store-header reader; bin-encoded headers refused; rename fault seam and the tests the survivors asked for
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>
2026-10-02 21:05:37 +02:00
Meta Vens
d350e150b0 Rust: bring back R1's leading-bytes readers for leading_header
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 19:49:01 +02:00
Meta Vens
f35867a232 Rust: a newer store cut short is still named unsupported; tests follow the single reader
R1's rule that the header alone decides an unreadable-format store (a newer store
cut short is not damaged) is kept as leading_header, asked before the whole-file
proof. A directory at the store path now stops a write at the read as an I/O
error, so the two tests that used it to reach a failed rename assert that instead,
and the two c2a header tests assert R1's UnsupportedFormat kind, not c2a's old text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 19:48:34 +02:00
Meta Vens
ff0251bbf8 Fix the R1 merge: close the Rust store_header visitor; a whole-store replace through a dangling link keeps replacing it
The merge left store_header's visitor unclosed (does not build). R3's pin that a
whole-store flush replaces a dangling link survives the single reader: the reader
still refuses the link as an I/O error, and the flush, which has no content to
carry from a link that holds none, writes v1 over it (Rust write_all_unlocked, TS
pushAll).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 19:46:39 +02:00
Meta Vens
bad511f5ac Merge CultCache R1 (f789c8d1) into C2a: one reader per runtime, R1's failure taxonomy
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>
2026-10-02 19:41:35 +02:00
Meta Vens
a771e71bd6 Rust test: pin the cargo-mutants survivors in the catalog lay-back
- The fixture gains an older entry listing the id ahead of the one that
  owns it, and the whole-store write carries the record under the
  registered type a Rust cache's pull gives it, not the entry's name.
  Kills the own-id lookup flip and the ||-to-&& flip in the entry filter.
- A store path that cannot be read (a directory) is an error, not an empty
  store (the NotFound guard had no test).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 22:59:52 +02:00
Meta Vens
eae56189c6 Rust: a write lays back the catalog entry the file holds for an id
C2a F2, widened by Self. The schema-identity probe showed a Rust write
replacing the C# catalog entry it read with a fabricated one: version
"sha256:...v1", members [], canonical JSON naming the hash.

- The store's one reader returns the file's catalog beside its envelopes.
- The encoder writes back the entry the file holds for each id it writes
  (by own id, else a lister) and describes an id itself only when the file
  has none, or when a registered type owns the id under another name (a
  rename, whose rule is unchanged).
- Test: a push and a whole-store push both lay back a C#-shaped entry byte
  for byte.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 21:20:11 +02:00
Meta Vens
f789c8d133 cultcache-rs: the document canary uses a value serde would quote
The first canary put a string where a u32 goes, and rmp-serde refuses that by
marker without quoting it, so the canary passed with the serde text back.
A string where a unit enum goes is quoted as an unknown variant. The canary
now covers get, get_all, get_all_with_keys, put_envelope and prepare_entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 20:35:47 +02:00
Meta Vens
cd93e4d112 cultcache-rs: keyed and document refusals keep no stored value; encode refusals are Rejected
- The redb envelope decode (get by identity and pull_all) and every typed
  document decode (get, get_all, get_all_with_keys, put_envelope, prepare_entry)
  kept serde's text, which quotes stored values. They now keep CultCache's
  context, which names the entry by type and key, and a fixed description of
  the decoder failure's kind.
- A snapshot the store cannot hold (two types claiming one schema id) was
  reported as NotReplaced, so a caller retrying on that kind looped forever.
  It is CultCacheStoreWriteFailedKind::Rejected now, raised before any I/O.
- release_lock and the read-only snapshot state one unlock policy: an unlock
  failure never replaces the result. Only the read-only path, whose API has an
  observer, reports it. release_lock also names the case where closing does not
  release the lock: a duplicate of the open file description, such as a child
  forked without exec.
- README: reads refuse a dangling link, but writes do not follow links yet. A
  write through a live link, and push_all through a dangling one, replace the
  link. Two tests pin that behaviour, named for R3 to decide between resolving
  and refusing. The write example classifies Rejected apart from NotReplaced.
- The shared-gate test waits 30 s and 60 s for the writer instead of 1 s, and
  checks that the file on disk is untouched while the gate is held.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 19:54:05 +02:00
Meta Vens
3c9e195b36 cultcache-rs: pin the text a failed write reports
cargo-mutants found no test of what CultCacheStoreWriteFailed says. Both
outcomes now assert the path and the outcome in the error text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 19:17:40 +02:00
Meta Vens
7472ede480 cultcache-rs: an unlock failure never turns a landed write into a failure
with_shared_lock, with_exclusive_lock, try_with_exclusive_lock and the redb
store's with_database returned the unlock's error in place of the action's
result, so a write that had replaced the store could be reported as failed
and read by Odin as not replaced. Closing the lock file releases the lock
whether or not the explicit unlock succeeds, so all four now release through
one helper that returns the action's result. The unlock failure is not
surfaced anywhere: there is no diagnostic channel on these paths.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 19:14:27 +02:00
Meta Vens
93ac594513 cultcache-rs: a failed store write says whether it replaced the file
Every single-file write now carries CultCacheStoreWriteFailed in its error
chain, above the cause. NotReplaced: creating, writing, syncing or renaming
the staging file failed, and the store file holds what it held before.
ReplacedNotDurable: the rename landed and the directory sync failed. Odin
could not tell these apart, so it kept every failed snapshot as a candidate.
The text names the path and outcome only.

fsync does not fail on a healthy filesystem, so the staging and directory
sync steps take a fault armed per thread by tests; builds outside tests never
fail there. The staging write (RLIMIT_FSIZE child) and the rename (a
directory at the store path) fail for real.

README: the write outcome, the dangling-link rule and the no-echo rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 18:37:59 +02:00
Meta Vens
c70dbd58be cultcache-rs: the header canary checks for the quoted header, not a prefix
"cultcache.store.v" is a prefix of the "cultcache.store.v1" the refusal names,
so the unquoted check failed on a correct refusal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 18:35:19 +02:00
Meta Vens
53cff052b8 cultcache-rs: refusals never echo store bytes; a dangling link is not absent
A decode refusal kept serde's text, which quotes values read from the file
(`invalid type: string "..."`). The detail is now CultCache's own context and
record refusals, which name only a record's key and schema id, plus a fixed
description of the decoder failure's kind. rmp-serde reports no position, so
none is given. An unsupported header is echoed only in the shape
`cultcache.store.v<digits>`; any other is described by its length.

`is_absent` used `fs::metadata`, which follows links, and a read that found
nothing through a dangling link returned an empty store, so a writer then
renamed a regular file over the link and the store left its volume. Only
nothing at the path is absent now (`symlink_metadata`), and a read through a
dangling link is an io::Error.

Tests: canaries for a catalog and a legacy value and for unshaped headers,
the dangling link on every read with nothing written, and a
`cultcache.storeX` header that claims no format.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 18:31:59 +02:00
Meta Vens
6acc414639 cultcache-rs: only a missing store reads as empty; an empty file is not a store
The single-file reads tested `path.exists()` and treated false as an empty
store. `exists()` is also false when the path cannot be inspected, so a
symlink loop, a file where the parent directory should be, or a parent
without permission read as an empty store, and a writer would then replace a
store it never saw. The read now maps only ErrorKind::NotFound to empty;
every other failure stays an io::Error. `pull_all`'s lock-free absence check
gets the same rule.

A store file that exists with zero bytes is Undecodable: no CultCache writer
produces one. `[0x90]` stays an empty store: it is the legacy envelope array
with no envelopes and carries no header, so it claims no format.

Refusal text: an unsupported header names the format found and the one this
runtime reads, and a v1 record with a fifth slot says the file is not a valid
store. Neither suggests a variant-aware runtime; the C# reference refuses the
same v1 bytes.

Tests pin a symlink loop and a file-as-parent on every read, the empty file,
the newer-header truncation boundary (1-19 bytes Undecodable, 20 and up
UnsupportedFormat), the refusal text, and staging cleanup when the rename
fails rather than the write.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 17:02:27 +02:00
Meta Vens
c0ab617e61 cultcache-rs: pin that only a cultcache.store header claims a format
A file that opens like a store but carries some other string where the
header goes is undecodable, not a store in a format this runtime does not
read. cargo-mutants left the guard's `true` mutant alive without this case.

The size-limited child's stdout and stderr now travel with the parent's
failure message, so a child that fails for another reason says which.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:17:45 +02:00
Meta Vens
dc2a0a7eaa cultcache-rs: a failed single-file write removes its staging file
Any failure after the staging file is created (a short write under a
file-size limit, a full disk, a failed sync or rename) now removes it before
returning the error. A write that failed for lack of space no longer keeps
holding the space it failed on until the next write sweeps it. C# parity with
WriteSnapshotAtomically's finally.

The test runs the write in a child process under RLIMIT_FSIZE with SIGXFSZ
ignored, so the failure is a real EFBIG mid-write, reached without
permissions.

Odin store-recovery map, cut R1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:39:52 +02:00
Meta Vens
c0b56e0674 cultcache-rs: type single-file read failures, read the header only
A single-file store whose bytes were read and not accepted now carries
CultCacheStoreUnreadable { path, kind } in its error chain, with kind
Undecodable or UnsupportedFormat, so a consumer can downcast_ref instead of
matching message text. A failure to read the bytes stays an io::Error and
never carries the type. The flattening map_err that turned every decode
error into a string is gone.

store_header reads only the array header and the first string instead of
decoding the whole array, so a store cut short after its header still names
its format: a truncated v1 store is an undecodable v1 store, not a legacy
decode mismatch, and a truncated newer-format store is UnsupportedFormat.

Odin store-recovery map, cut R1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:39:51 +02:00
Meta Vens
1a04b0d788 Rust: a registered type owns its schema id, so only it may name an id over records the file holds
The previous cut let any supplied type name a schema id over the arrived one, which let a raw
envelope of another type retype the records under an id (the dynamic-payload test refused it and
failed). A rename under a stable id is a write of the registered type that owns the id; a Rust
registered type's id is its entry type, so a supplied envelope whose type is its schema id names
the id and any other type under an occupied id is refused, typed, with the file left as it was.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 11:10:06 +02:00
Meta Vens
6f219bd919 Rust: a record resolves by schema id, and the registered type names a renamed schema id
pull_all_backing_stores resolves a record to a registered type by its schema id (a registered
type's id is its entry type) before the catalog name is tried. The writer tells the entries the
file already holds from the ones its caller supplies, and a supplied type names the schema id over
an arrived one, so a stable-id rename is not a conflict; two types of one tier under one id still
refuse the write.

Tests: a stable-id rename write, a renamed schema opening by id, and an id not overruled by a name.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 11:04:11 +02:00
Meta Vens
e77246b657 Writers derive the catalog from the records they write; the merging is deleted
Every writer (C# single-file and directory, TypeScript, Python; Rust already did) chooses, for each schema id a record carries, one
entry that publishes it: the entry that owns the id (a registered descriptor, else an arrived entry), else one that lists it as
compatible. A registered descriptor wins over an arrived entry with the same own id. Entries are written as chosen, with no union of
compatible ids and no dependence on arrival order; an entry no record needs is not written. Two entries of one tier that share an own id
and disagree on the schema name, records of different types under one id, or a record no chosen entry publishes refuse the write with a
typed error (CultSchemaConflictException, SchemaConflictError) naming the id, the names and a record key, and the store is left as it was.
CultSchemaCatalogEntry.MergeById and the Python and TypeScript merge paths are gone. A cache-held record unchanged since the file was read
is rewritten under its registered id (C# single-file and directory), so an entry that lists an older id is not needed to publish it.
Changelogs for cultnet-py (removed export) and cultcache-py.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 10:03:34 +02:00
Meta Vens
59f9e9aedb One local schema id in cultmesh-py; catalog writers merge only the same schema; a compatible entry before its owner reads as the owner
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>
2026-09-30 09:01:00 +02:00
Meta Vens
f96a0e7f7a Catalog writers merge entries that share an id; every reader resolves an id by its owner first
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>
2026-09-30 08:17:59 +02:00
Meta Vens
c6f87fd3bf Rust: compatible schema ids publish a schema, and the refusal's message carries its cause
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 06:54:17 +02:00
Meta Vens
ed61462a70 Tests: the recovery tests assert the refusal
Rust, TypeScript and Python each had a test that a record missing its catalog entry is recovered from its payload; each now asserts
the store is unreadable.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 05:58:47 +02:00
Meta Vens
3a3de56341 Store readers: a schema is never recovered from a record's payload
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>
2026-09-30 05:53:01 +02:00
Meta Vens
91d464a038 Rust test: a refused store's cause is the error's source
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 04:23:02 +02:00