Rust leaf: ownership map's Rust cells, the changelog's envelope-id and redb load consequences, the vector's catalog spelling note
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
c0d0730f7b
commit
33f5679fc4
3 changed files with 12 additions and 7 deletions
|
|
@ -33,8 +33,8 @@ Probes: scratch tests in this worktree, removed. C# filtered runs on Starfire (o
|
|||
|
||||
| Probe | C# | TypeScript | Rust | Python |
|
||||
|---|---|---|---|---|
|
||||
| P1 cache {A,B}: B at k, then A at k | **C**: B's record replaced, k now A's id | C, and **both** records live at k | C, both live at k | C, both live at k |
|
||||
| P2 cache {A} only, store holds B at k, write A at k | R `CultSchemaConflictException` (B is foreign, carried) | R at load: "No schema is registered for persisted schema id" | R at load, same | R at load: "Unknown persisted document type" |
|
||||
| P1 cache {A,B}: B at k, then A at k | **C**: B's record replaced, k now A's id | C, and **both** records live at k | R `SchemaConflictError` naming k, A not written (leaf cut) | C, both live at k |
|
||||
| P2 cache {A} only, store holds B at k, write A at k | R `CultSchemaConflictException` (B is foreign, carried) | R at load: "No schema is registered for persisted schema id" | R `SchemaConflictError` (B loads as foreign, carried; leaf cut) | R at load: "Unknown persisted document type" |
|
||||
| P3 cacheless `WriteDocument<A>` over B, registry null / {A} / {A,B} | R / R / R | no cacheless write | no cacheless write | no cacheless write |
|
||||
| P4 cache {A,B}: remove k and upsert A at k in one batch | C (k is A's) | n/a | n/a | n/a |
|
||||
| P5 cache {A,B}: remove, then add in a second commit | C | n/a | n/a | n/a |
|
||||
|
|
@ -55,10 +55,12 @@ What this establishes:
|
|||
- Retype as remove plus add already works in the cache (P4, P5); the cut adds no new primitive.
|
||||
- Directory store: its own judge (`DirectoryMessagePackBackingStore.cs:287-306`) asks `Registry.Owns` for every key,
|
||||
upserts included. It must take the same per-key rule.
|
||||
- TypeScript, Rust, Python: records are keyed by `(type, key)` (Rust `entries: BTreeMap<(String, String), _>`,
|
||||
`lib.rs:2659`; TS `#entryId(definition.type, key)`, `cult-cache.ts:323`; Python sorts and stores by `(type, key)`).
|
||||
A write of A at k never meets B's record: both live at k (P1). None carries a foreign record; a store holding an
|
||||
unregistered type does not load (P2). So no leaf has a "write meets another type's record" path to fix. Bringing the
|
||||
- TypeScript, Python (and Rust before its leaf cut): records are keyed by `(type, key)` (TS `#entryId(definition.type, key)`,
|
||||
`cult-cache.ts:323`; Python sorts and stores by `(type, key)`).
|
||||
A write of A at k never meets B's record: both live at k (P1). Neither carries a foreign record; a store holding an
|
||||
unregistered type does not load (P2). Rust, after the leaf cut, keeps its `(type, key)` map but its one judge
|
||||
(`refuse_retype`) refuses a second registered type at a held key on every write path, pull and SoA load, whatever schema
|
||||
id an envelope claims, and carries an unregistered type's records as foreign; it walks the vector's 12 rows. So no leaf has a "write meets another type's record" path to fix. Bringing the
|
||||
ruling to the leaves first needs C#'s key identity (one record per key) and C#'s foreign carry, which are parity changes
|
||||
of the leaves' store model, not a write-ownership rule. Owned by the new follow-up `variants:follow_up:leaf-key-identity-parity`.
|
||||
- Interop (unproven): a Python store holding A and B at k fails C# decode first on payload encoding (Python's
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ only in git history.
|
|||
- A single-file store holds one record per key. The v1 and legacy readers refuse a store with two records under one key (`CultCacheStoreUnreadable`, the record `Undecodable`): the store does not open, and a write over it refuses and leaves the file. A consumer that holds such a file must repair it with the runtime that wrote it before opening it here.
|
||||
- Every single-file writer (`push`, `push_all`, `insert_entry_if_absent`, the compare-exchange and snapshot-append family, and the cache's puts and batches) refuses to put a second type under a key the store holds, with `SchemaConflictError` naming the key and both types, writing nothing. Retyping a key is a delete, then an add (one batch may hold both). A write owns the held record when its schema id equals the id the held record is stored under, as C# `WrittenOwns` asks; a type renamed under a stable id therefore replaces its own record, and a write of another type name under another id is refused (`push` of the same type name under another id still replaces the record, as before; `compare_and_swap_entry`, and a `push_all` batch holding one type under two ids, refuse it). Registered types here declare no compatible ids, so equality is the whole test. Writers used to leave two records under the key.
|
||||
- `compare_and_swap_entry` over a single file refuses an in-place retype (expected `A/k`, replacement `B/k` under another schema id) with the same `SchemaConflictError`, as `compare_exchange` does, where it used to retype the record and return `true`. A lost race still returns `false` first.
|
||||
- A cache holds one record per key. `put`, `put_envelope`, `put_raw_envelope` and a batch of a second type under a key the cache holds, or a key a store carries foreign, are refused with `SchemaConflictError` even with no store (a memory-only cache used to hold both); a refused write or pull changes nothing. Two stores holding one key refuse the pull, naming the key and both types. A store holding a record no registered type claims now loads (it used to refuse with "No schema is registered for persisted entry type") and the record is carried, through `put_prepared_batch` too, which lays foreign records back byte for byte; `delete` at a foreign key is refused. `load_soa` refuses a second type at a key and still refuses an unregistered type. The redb stores still key by `(type, key)`.
|
||||
- A cache holds one record per key. `put`, `put_envelope`, `put_raw_envelope` and a batch of a second type under a key the cache holds, or a key a store carries foreign, are refused with `SchemaConflictError` even with no store (a memory-only cache used to hold both), whatever schema id the envelope claims: another registered type's record is never owned, an envelope stamped with the held type's id included; a refused write or pull changes nothing. Two stores holding one key refuse the pull, naming the key and both types, and so does one store holding two records at a key that resolve to one type. A store holding a record no registered type claims now loads (it used to refuse with "No schema is registered for persisted entry type") and the record is carried, through `put_prepared_batch` too, which lays foreign records back byte for byte; `delete` at a foreign key is refused. `load_soa` refuses a second type at a key or a record at a foreign key, and still refuses an unregistered type. The redb stores still key by `(type, key)`, and their own writers (`push`, `insert_entry_if_absent`, compare-and-swap) still put a second type at a held key: a cache now refuses to pull such a redb store with `SchemaConflictError`, where it used to load both records.
|
||||
- A write of the same type under a held key replaces the record by key, and in one `push_all` the last given of several entries under a key and a schema id is the record, whatever their type names. The redb stores are unchanged and still key by type and key.
|
||||
|
||||
### Added
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ Registering a type that declares its schema id claims it at the next load.
|
|||
- `tests.ki_alpha`: `sha256:b20703a98f49c253898404ab50ba2c630720f0b8a5e1a41614d5650be22799fe`
|
||||
- `tests.ki_beta`: `sha256:dee4b727cf2ee04e61ab635ff0c5f3dca7345a411dc5f1018e583ab16009cf94`
|
||||
- `tests.ki_gamma`: `sha256:fbc26e351e00c9e7185e8de171c7486aba707c229c949f0aae6b889288af5f6d`
|
||||
The Rust writer names a catalog entry it writes by the record's type, which for these types is the id, where C# names it by
|
||||
schema name; the Rust walker (`packages/cultcache-rs/tests/key_identity_vector.rs`) maps each of the three ids, and only
|
||||
those, back to its schema name before comparing `file`, so the verdict is compared and the spelling divergence is not.
|
||||
- `foreign-store.cc`: a single-file store, alpha `a` at key `j`, gamma `g` at key `k`, stored-at pinned to
|
||||
`2026-01-01T00:00:00Z`. `catalog-store.cc`: alpha `a` at `qx`. `run-store.cc`: beta `b` at `qx` (row 11). The row-11 key is `qx` because it must
|
||||
occur in the refusal's message only as the key, never inside a schema name (`k` is inside `ki_alpha`).
|
||||
|
|
|
|||
Loading…
Reference in a new issue