9 KiB
C2a land map
What C2a (hands/variants-c2a, a5c7954e) must still prove to merge to main, measured
on 2026-10-22 against main 7a8933b0 and the 2.0.0 release candidate
hands/cultlib-unity-release-2.0.0 (5b5d8573, which carries f91e30ed). The land cut
is the merge plus the gate. Everything else is classified below. Governing ruling:
cultlib-gaps:ruling:release-now-c2a-bumps-format.
Facts
1. The merge. A trial git merge origin/main on a5c7954e gives 12 conflicted files and
33 hunks of about 950 marker lines (CultCache.cs 17 hunks/196; cult-cache.test.ts 337;
single-file-messagepack-backing-store.ts 4/106; rs lib.rs 2/51; VariantTests 51; three
CHANGELOG add/adds; store-composition contract 32; Directory store 14; InteropPeer 21).
Main moved 4.8k lines in the cache runtimes since base 1a12bc2d. Three semantic collisions
are not textual:
- Durable record identity has two owners. Main added
CultStoredDocument.PersistedSchemaIdandCacheBackingStore.MarkWritten(73d438db,afd799ba,3f6dda8b). Conditional commit compares the persisted schema id plus storedAt. C2a already owns this asStoredSchemaId+StoredVersion(SHA-256 of the stored bytes, which names the schema id and storedAt) set byCultCache.Wrote. After the merge, 9 references in ConditionalCommitTests and the contract name main's owner. The version is the owner. PersistedSchemaId and MarkWritten are deleted, and main's +309 test lines are ported as pins against StoredVersion. 2.0.0 ships MarkWritten as protected API, and C2a also removesSerializeSnapshot/DeserializeSnapshotoverrides and changesConditionsHold. C2a is therefore a major release (3.0.0), which semver forces. - TS store commit moved. Main's
store-file.ts(0e6452c9,0010ee92; 391 lines) now owns commit, replace and the<path>.lock. C2a's v3 read and keep-v3-on-rewrite (STORE_FORMAT_ELEMENT_IDS) lives in the old store file and must move into store-file.ts. - Rust decode door. Main's cultcache-rs 0.3.0 grown decoder (
decode_stack,fc630d3b) must sit under C2a'sreadable_store_formatand v3decode_store_file.
2. The gate already holds; it needs a test, not a format change. C2a stamps
cultcache.store.v3 (single file) or ...v5.directory-content-addressed-pages exactly when
a record holds an id (HeaderFor, CultCache.cs:3543). It also marks when a copied record
decodes holding one (CopiedHoldIds, :4285). The 2.0 C# reader accepts only v1/v2
(CultDocumentMessagePackSerialization.cs:229) and directory v4 (Directory store :452).
Main's TS, Rust and Python readers accept only v1 (store-format.ts isV1Snapshot, lib.rs:1274,
stores.py:192). Probe on Yggdrasil at 5b5d8573 plus C2a's v3-base.msgpack (job
gate1.log): 4/4 pass. The single file is refused naming v3, and a later flush leaves it
byte-identical. A v5-stamped directory store is refused naming v5, and its 4 files are
byte-identical after a later add and flush. The pin: C2a already ships
tests/vectors/document-variants-c2a/old-reader-refusal.sh, which stages an old revision
plus probes on a scratch ref and runs them per runtime. The land cut sets its
OLD_READER_REV default to the tag cultlib-unity-v2.0.0. It also adds the never-rewritten
assertion and the v5 directory case to the C# probe, and runs all four runtimes at the
tag. If the tag is absent, the land waits for it. The tag, not a package, is the pin: the tag's
tree holds every runtime's released reader.
Outside C2a's reach (release session): in 2.0, CultMesh.WriteSingleFileDocument/
WriteSingleFileDocumentPayload (CultMeshSingleFileDocuments.cs:211-245 at 7a8933b0) build
a fresh one-record v1 snapshot and replace the file without reading it. A 2.0 Mesh write
to a C2a v3 path therefore discards it whole, ids and other records included, and no header
gate can stop a writer that never reads. C2a's version reads under the lock and refuses.
Fix it in 2.0.0 before tagging, or record it.
5. Consumers. Aetheria pins a CultLib revision (VerifyCultLibRevision) and Unity tags.
Ghostlight, Eve's Rust crates, Huginn and the Brokkr daemon pin cultcache-rs by git rev.
None of them moves when main moves. Two C# consumers build against an unpinned sibling
..\CultLib checkout: Eve GameCult.Eve.Surface.csproj:16 and Brokkr's three csprojs.
Eve.Surface has [MessagePackObject] element arrays with no [CultElementId]
(EveAssetCatalogEntry[], EveEntitySoaBuffer[], EveInputActionDocument[],
IReadOnlyList<EveCommandTemplate>, 0 ids in the repo), so C2a registration refuses its
documents as soon as the checkout pulls the land. Grep finds no object lists in Brokkr's
[CultDocument] files (not build-verified). Removed C2a API (Options, AsSchemaAlias) is
unused by Eve and Brokkr. Fork: variants:question:unpinned-consumers-at-land.
Classification
| Item | Class | Why |
|---|---|---|
| c2a-format-version-gate | a | The gate test above. No new header: v3/v5 already refuse. |
| slot-reuse-contract-doc | a | The ruled consequence of the write set C2a lands. One paragraph in cultcache-schema-compatibility.md, absent at a5c7954e (grep). |
| foreign-ids-under-v1-header | b | Obsoleted by fb065f0d (write-time CopiedHoldIds, after the finding) for every C# writer with the type. The residual (non-C# or opaque writers) is what non-csharp-writers-mark-ids-2 ruled to carry, and the contract states it (persistence-format.md:434-439). The gate changes neither side, so no fork remains. Resolve as Superseded by that ruling. |
| custom-resolver-element-ids | b | Fixed by b091d21f (19:52Z, after the 18:27Z follow-up): ListElementsClaimedByAResolverAreRefused, AResolverClaimingTheListTypeOfAnObjectListIsRefused. s6 holds on the claim loop. |
| codec-door-proven-mismatch | b | Done in h6 57fdf70c (s6 only-proven-mismatch Holds; Geometry 76/0). |
| consumer-element-id-sweep-r2 | c | Each repo's sweep is its own work (q3). Only the unpinned trackers matter at land, and that is the question below. Aetheria bumps a pin on purpose (C4). |
| schema-discovery-holder-recogniser | c | TryGetDictionaryRefTypes is identical on main (CultCache.cs:1288). It describes record-ref dictionaries and is not on the id path. Changing it moves schema hashes, which is its own epoch event. |
| copy-check-memo | c | Performance only (about 3x flush on unmarked stores of 20k records). Correct as is. |
| string-key-field-and-shadow-pins | c | No wrong behaviour at 64ec14f9. These are test pins and may ride the land cut if Hands touches the file. |
| declared-range-gate-own-version, range-check-in-ci | c | The script exists only on C2a and runs in no CI. It guards a TS publish, not a merge. Fix both before the first TS release from main after the land. |
| s6 Unproven: Unity/IL2CPP identity, other MessagePack versions | c | The merge reaches no Unity build. Unity sees C2a only at the 3.0.0 tag, so measure it in the Unity release cut (operator Unity check, C3). |
| multi-store-commit-atomicity, store-replace-cut, reader-refuses-duplicate-keys, soft-flush-all-runtimes, writer-dangling-link-tolerance, unique-index-cut, py-shared-write-lock, rust-lock-open-waits, ts-lock-test-hardening, c0/legacy/directory vectors, culture-test, wire-id-cuts, remaining-variant-cuts, golden-path, paged store | c | None says "before land". Each is pre-existing on main or C2a-independent. None lets a released reader strip ids or corrupt a C2a store. |
The land cut's shape (transcribe once the question is ruled)
- Base. Merge main at or after the
cultlib-unity-v2.0.0tag intohands/variants-c2a. Versions stay main's. CHANGELOGs get Unreleased entries, and the Unity entry says major. - Deletes and collapses. Delete
PersistedSchemaId,PersistedIdentity,CultStoredDocument.MarkWrittenandCacheBackingStore.MarkWritten(main, about 60 lines), and the store-composition contract's MarkWritten paragraph. C2a'sStoredVersion/Wroteis the one owner. In TS, delete C2a's header handling in single-file-messagepack-backing-store.ts and move it into store-file.ts. In Rust, C2a's v3 decode goes through the grown decoder. - Adds. Retarget old-reader-refusal.sh to the tag. Add the C# probe's never-rewritten assertion and the v5 directory case (as in gate1.log). Add the slot-reuse paragraph. Port main's ConditionalCommitTests to the version identity.
- Verification. Full suites: Caching, Mesh, Networking, Geometry, WebSockets,
ConsumerBuild, InteropPeer, cultcache-ts/-rs/-py, cultnet-ts and cultmesh-ts, all on
Yggdrasil. Run old-reader-refusal.sh for all four runtimes at the tag, and it must refuse.
Negative greps over the merged tree:
PersistedSchemaId|MarkWritten, conflict markers. Mutation: drop the v3/v5 stamp inHeaderForand the gate probe must fail. - Estimate. About 950 conflict lines resolved. Net -60 to -150 from the identity collapse and TS de-duplication, +80 to +120 tests and docs. One Hands pass of 100-150 calls, then one Soul pass.
- Precondition (question). Eve.Surface, and Brokkr if confirmed, are pinned or swept before the merge reaches main.