cultlib-gaps map: Expect under compatible schema drift (E1-E7) and the cut line

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-21 20:57:54 +02:00
commit 5d81761ee1

View file

@ -27,6 +27,10 @@ copy of the solver and the warm/cold sweep, `suite` a scratch copy of `packages/
key tests read C#-written bytes instead of literals.
7. `decode-stack-safety` (section D): cultcache-rs's one decode door grows its stack at every container level
(ruling `decode-stack-mechanism-ruled`). Independent of 5 and 6; thing2's contract-rs merge waits on it.
8. `expect-persisted-schema` (section E): an observed entry keeps the schema id it was persisted under and
conditional commit compares that (ruling `aetheria-release:ruling:expect-fix-in-cultcache`). C# only. The
release that carries it to Aetheria follows question `cultlib-gaps:question:expect-fix-release-lane`; the
Aetheria pin bump is an Aetheria follow-on, not this cut.
## B. BoundedLeastSquares stopping
@ -684,6 +688,65 @@ door-checked `rmpv::Value`s that way (follow-up `rs-redecode-outside-door-r2`).
Rejected inside the ruling: the derive placement (above), and writing the wrapper by hand (D9: established
tooling, one owner upstream).
## E. Expect under compatible schema drift (cut `expect-persisted-schema`)
Pinned base: `origin/main` at `b549d731` (2026-10-21). Imagination pass `imagination-expect`, session
`self-2026-10-19-ag7`. Probe `probe1` (`ProbeExpectDrift.cs`, written into the clone by the job command, never
committed) ran on Yggdrasil, dotnet sdk 10.0, against `b549d731`. Fixture: a record written under
`DriftWide` (`tests.variant_drift.v1`) and read by a cache whose registry holds only `DriftNarrow` (v2) loads
as CompatibleDrift.
### Body facts
- **E1. The defect.** `CultCommitRequest.ConditionsHold` (`CultCache.cs:1677-1696`) compares the durable
record's `SchemaId` with the observed entry's `Descriptor.SchemaId` (`Expect` captures it at `:1772`;
`ExpectUnchanged` reads it at `:1694`). `ToStoredDocument` (`:3212-3268`) resolves a persisted record to the
local descriptor and drops `record.SchemaId`. So the observed side holds the local id and the durable side
the persisted id. Probe: unchanged drifted record, `Expect` + `Upsert` commits **False** on the single-file
store (P1) and on the directory store (P7). Same code at Aetheria's pin `45c2f40` (`CultCache.cs:1147-1207`).
- **E2. What must still refuse, at base.** Another cache commits a changed value (new minted `storedAt`):
Expect False (P4). Another cache reading under v3 (`DriftReused`) flushes the file, rewriting the record under
v3 with the **same** `storedAt`: Expect False (P5).
- **E3. A whole-file write rewrites every record under its local schema and keeps `storedAt`.**
`ToPersistedRecord` (`:3194-3204`) writes `entry.Descriptor.SchemaId` with `entry.StoredAt`, and both the
single-file `PushAll` (`:3361-3375`) and an unconditional or dirty `CommitBatchCore` (`:3393-3399`) write the
whole view. So the contract sentence "every write to a key mints a later storedAt"
(`Contracts/cultcache-store-composition.md:229-231`) is false for a flush: the schema id is the half of the
identity that sees it. The directory store writes only staged keys, each freshly stamped
(`DirectoryMessagePackBackingStore.cs:240-293`), so it never normalises a clean record.
- **E4. Lifetime at base.** After the cache's own `PushAll`, Expect commits True (P2): disk and descriptor now
agree. After *another* cache's normalising flush to the observer's own local schema, Expect commits True (P3);
after a re-pull, True (P6).
- **E5. Re-reads.** Both pull filters skip a record whose `storedAt` and local descriptor id are unchanged
(`CultCache.cs:3323-3327`, `DirectoryMessagePackBackingStore.cs:110-114`). A record normalised P to L by
another writer keeps `storedAt` and the local id, so with the persisted id on the entry the filter must
compare the persisted id, or a re-read never refreshes it and Expect stays a Mismatch forever.
- **E6. Other runtimes.** cultcache-rs `SingleFileMessagePackBackingStore::compare_exchange` and the snapshot
form (`packages/cultcache-rs/src/lib.rs:1352-1393`, `:1427-1450`) compare whole raw envelopes as read from
disk (`schema_id: Some(record.1)`, `:3189`); the expectation *is* the persisted envelope, and the crate has
no compatible-drift resolution, so the split cannot occur. Unchanged. cultcache-ts and cultcache-py have no
conditional commit (`cultcache-persistence-format.md:67-69`; grep finds none); nor do the cultnet and
cultmesh packages or cultmesh-kotlin. Not added here.
- **E7. Release path.** Aetheria pins `org.gamecult.cultlib#cultlib-unity-v1.0.60` and
`org.gamecult.caching.unity#caching-unity-v1.4.0`, both locked at `45c2f400` (2026-09-17). The CultCache
runtime ships as DLLs in `unity/org.gamecult.cultlib` (built by `scripts/build-unity-package.ps1`). Main is
1363 commits past the pin with a `### Breaking` section under `[Unreleased]`
(`unity/org.gamecult.cultlib/CHANGELOG.md`). `release/cultlib-unity-1.0.x` is the pin plus one commit
(1.0.61, Win64 QUIC plugins).
### Authority, and why
The backing store that read or wrote a record's durable bytes owns the schema id it is persisted under. It is set
when the store builds the entry from a persisted record (`ToStoredDocument`), defaults to the descriptor's id
for an entry this cache stamps (it is about to be written under that id), and is reset to the descriptor's id,
under the hold, after a whole-file write the store completed. The commit check, `Expect` and the pull filters
only read it. `Descriptor.SchemaId` stays what the entry decodes as; it is no longer the record's durable identity.
Consequence, recorded rather than ruled: after another writer's whole-file flush rewrites a drifted record
under a schema id this cache did not read it under, an `Expect` captured before is a Mismatch until the cache
re-reads (E4's P3 flips). That is the ruling's "compares that persisted id" applied to a rewrite that kept
`storedAt`; it is fail-closed, the standard retry (re-read, retry) recovers, and it is what refuses E2's P5.
## Observed, not owned by these cuts
- At the default tolerance the solver's answer on allocator-shaped problems depends on the start by up to the whole