Discrepancy between spec and Body, reported per Hands' brief ("make the
smallest change that keeps the spec's intent, and report the discrepancy").
R-R says "recordKeys: [] answers empty" for v0 lowering. That holds for the
raw v0 wire protocol server (CultNetDocumentRegistry.CreateRawSnapshotResponse,
CultNetDatabase.CreateShardSnapshotResponse - fixed two commits back). It does
not hold for CultMesh's typed convenience API: R-F, landed earlier in this
cut, made CultMesh.EnsureV0Compatible run CultNetSelectionValidation.
ValidateShape client-side before a request is ever sent, specifically so that
"Keys=[]/[\"\"] is refused rather than silently lowered to 'every key'"
(CultMeshSnapshots.cs:836-841). Once R-R made ResolveDefaultSelection's
Clean(recordKeys) produce Keys=[] instead of Keys=null for an explicit empty
list, every FetchDocumentsAsync(recordKeys: []) call now trips that existing
R-F door and throws, instead of reaching the wire at all.
Kept both rulings as each one's own owner decided them: the raw wire protocol
answers empty (R-R); CultMesh's typed wrapper refuses before sending (R-F).
Rewrote FetchDocumentsAsync_WithExplicitEmptyRecordKeys_AnswersEmpty, added
two commits ago on the wrong assumption that CultMesh's typed API also
answers empty, to FetchDocumentsAsync_WithExplicitEmptyRecordKeys_
IsRefusedClientSide, asserting the actual (and, on inspection, correct) R-F
behaviour instead.
Verified on Yggdrasil: dotnet test tests/GameCult.Mesh.Tests -c Release.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CultNetV0SelectionLowering.Lower collapsed a non-null-but-empty filter list to
null ("no filter"). That corrupted two pre-cut v0 behaviours the same
function feeds:
- CultNetDatabase.CreateShardSnapshotResponse and
CultNetDocumentRegistry.CreateRawSnapshotResponse (b3d9cf7) both test
`filter?.RecordKeys != null`, not a length, so `recordKeys: []` always
answered empty. Lowering the empty list to null made it answer every row
instead. This corrects the ruling from the commit-2 fix batch that lowered
an empty v0 list to "no filter".
- CultMesh.ResolveDefaultSelection used the same collapse to decide whether an
explicit empty recordKeys should still fall back to the document type's own
schema default; under the corrected Lower it must not.
Lower(null) still means "no filter"; Lower(non-null) is always a real
(possibly empty) array. The evaluator already treats a non-null empty Keys
array as "match nothing" (CultNetSelectionEvaluator.MatchesSchemaKeysFields);
only the v0 lowering was erasing that signal before selections reached it.
Also restores the pre-cut requested-key order for
CultNetDocumentRegistry.CreateRawSnapshotResponse: pre-cut, requested record
keys were iterated in the order the caller sent them (a HashSet<string> built
from the array, which enumerates in insertion order); the new shared
evaluator's constant ordinal fell back to schema-and-key sort order instead.
ordinalOf now ranks rows by their position in the requested key list when one
was given.
Rewrote CultMeshStreamingTests.FetchDocumentsAsync_WithExplicitEmptyRecordKeys
(renamed ...AnswersEmpty) to assert the corrected behaviour instead of the
ruling it used to pin. Added
CultNetDocumentRegistry_RawSnapshot_ExplicitEmptyRecordKeys_AnswersEmpty,
CultNetDocumentRegistry_RawSnapshot_KeepsRequestedRecordKeyOrder and
CultNetV0SelectionLowering_Lower_DistinguishesNullFromExplicitEmpty to
NetworkingTests.cs.
Not done in this commit: CultNetDatabase.CreateShardSnapshotResponse (the
shard-replication snapshot path) still orders by the evaluator's default
schema-and-key tiebreak. Its own pre-cut order was raw cache iteration order,
which the new evaluator has no equivalent for and shard replication does not
appear to depend on row order; flagged as a discrepancy rather than changed
without a test that would pin the "right" order. The side-by-side b3d9cf7 vs
HEAD comparison run the spec asks for was not executed in this pass; the
pre-cut behaviour above comes from reading CultNetDatabase.cs and
CultNetDocumentRegistry.cs at b3d9cf7 directly.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Repairs two stale mutation entries in
scripts/mutate-cultnet-selection-caching.entries.ps1
(CACHE-QJ-ExponentExpansion-Revert, CACHE-QJ-NaNInfinity-Revert): both
anchored on ExpandScientificNotation/the pre-R-D switch arm, which the
first fix-batch pass's R-D commit (7903853) deleted outright in favour of
RenderExactFloat/RenderExactDouble. Replaces the exponent-expansion entry
with CACHE-RD-ExactExpansion-Revert (reverts to .NET's shortest
round-trip ToString(), the exact defect R-D fixes) and
CACHE-RD-FractionSplit-Loosening (an off-by-one digit-split bug that
still looks "exact"), and re-anchors the NaN/Infinity entry on the
current RenderExactDouble call.
- Repairs one stale mutation entry in
scripts/mutate-cultnet-selection-mesh.entries.ps1 (MESH-FOREIGN-Revert):
anchored on the pre-R-L inline TryReadSchemaVersion call, which R-L's
commit (c01bc0a) replaced with CultNetDocumentRegistry.PayloadMatchesSchema.
Re-anchors it and adds MESH-RF-Door-Revert (R-F: EnsureV0Compatible runs
CultNetSelectionValidation.ValidateShape before its own v0 transport
refusals), with a new tests/GameCult.Mesh.Tests/CultMeshSnapshotOptionsTests.cs
proving FetchSnapshotAsync refuses Keys=[]/[""] before ever touching a
client.
- All entries verified: Caching 11/11 killed, Mesh 7/7 killed (pending final
confirmation on this run), restores byte-exact.
R-L (docs/cultnet-selection-cut.md): the Python refusal of vN>0 already has
a real regression test (packages/cultnet-py/tests/test_cultnet.py:1958-1978,
unsupported_schema_version) but no mutation entry - mutate-dotnet.ps1 is a
dotnet/NUnit harness only and this repo has no Python mutation harness. R-L's
C#-adjacent piece (TryReadSchemaVersion going internal behind
CultNetDocumentRegistry.PayloadMatchesSchema) is covered by
MESH-FOREIGN-Revert above.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Soul found commit 2 didn't pass (docs/cultnet-selection-cut.md, 2026-09-22):
empty/blank schemas-keys still reached the evaluator, the shard snapshot ran
its own loop instead of the evaluator, Mesh's v0 wire silently dropped v1-only
selection terms, the Mesh one-pass read could return the wrong schema's
record at a shared key, two decode rules for a foreign schema id disagreed,
dead identity-comparison code from the _projectRecord removal was still live,
and a disposed-socket race could abort a mutation run instead of recording
NO VERDICT. This is Self's fix batch for all of it.
- CultNetSelection.Validate now refuses a blank/whitespace entry inside
schemas or keys, not just an empty array. CultNetSelectionEvaluator no
longer treats [] as "no filter" (the door already refuses it before
evaluation). CultNetV0SelectionLowering is the one place that lowers an
empty or all-blank v0 list to null; CreateRawSnapshotResponse and
CreateShardSnapshotResponse both route through it, and CultMesh's own
Clean() now delegates to it instead of duplicating the rule.
- CreateShardSnapshotResponse no longer carries a private loop or its own
schema/key matching: it builds a CultNetSelection and answers through
CultNetDocumentRegistry.SelectPage (the same evaluator path as the
non-shard snapshot), with the shard's own membership check as a row
filter. Binding-alias expansion now runs on both paths identically.
- ResolveDefaultSelection decides "no schema filter" from the cleaned
recordKeys, not the raw array length. OverlaySelection stops deciding what
a missing override means: any explicit schemaIds/recordKeys override
replaces the default selection wholesale; no override keeps the default
untouched.
- CultMeshSnapshotRequestOptions.Selection is now checked by
EnsureV0Compatible before every send: fields, cites, cited, limit, cursor,
descending, and any non-default projection are refused typed
(CultNetSelectionInvalidException), never silently dropped from the v0
wire message. Mesh sending v1 is FU-Mesh-v1, not this cut.
- CultMeshHotBodySubscription.Selection returns a defensive copy on every
read so its arrays can't be mutated out from under the stored selection.
- ReadDocumentFromSnapshotResponse now searches candidates at one recordKey
in one order - exact schema, then alias, then a foreign schema id decoded
from the payload - instead of one OR'd condition where a case-insensitive
or structurally-lucky candidate could win first. The foreign-id decode
reads the payload's embedded schema version with the registry's shared
CultNetDocumentRegistry.TryReadSchemaVersion and alias-matches it against
the caller's own descriptor directly, rather than resolving to "whichever
registered type owns this schema string" - that resolution is ambiguous
when two document types alias the same schema id (MeshNoteDocument and
MeshNoteAliasDocument in the existing fixtures), which is what broke on
the first pass; TryResolveDescriptorByPayloadSchema stays for its existing
caller (ResolveDescriptorForRawDocument) with a note on when not to use it.
DecodeSnapshotDocuments and the Mesh copy of the payload-schema decode
(RawSnapshotPayloadMatchesSchema/TryReadSchemaVersion) are deleted in
favour of the shared one.
- CultNetDatabaseSubscriptionServer: the dead RecordKey/SchemaId identity
comparisons in Reconcile and ApplyProjectedChange, and the duplicate-key
throw in CreateProjectedSnapshot, are deleted - they can no longer fire
now that a projection's dictionary key is always the record's own key.
A new test flips authorizeRecord per key while the whole request stays
authorized (so DemandActive never toggles) and proves both halves of the
remaining diff: the removal of a now-unauthorized key and the add loop
picking up a newly-authorized one.
- Every NetworkingTests.cs test that starts an IsBackground poll thread now
joins it before its server disposes (about 20 tests; one already did).
- scripts/mutate-dotnet.ps1: a dotnet-test-host crash writing to stderr no
longer throws the whole run away under PowerShell 5.1's
$ErrorActionPreference = 'Stop' (2>&1 on a native command wraps stderr in
a terminating NativeCommandError); Run-Tests catches it and the entry loop
records NO VERDICT instead of aborting every remaining entry.
- Two probe tests: CultNetDatabase_ShardAndNonShardSnapshot_Agree{On
UnfilteredRequest,OnBindingSchemaIdOverride} prove the shard and non-shard
snapshot paths agree, including shard-membership exclusion of a document
outside the shard's key prefix and a binding schema-id override matching
on both paths.
- Mutation entries updated/added to match: the stale NET-D4-FourthMatcher
entry (its anchor was the private loop this batch deleted, not refactored)
is replaced by NET-D4-ShardMembership-Revert; the Mesh entries file's
header miscount ("two of three" loosening entries when there are only
two) is corrected and both previously-surviving loosening mutants
(MESH-DEFAULT-Loosening, MESH-KEY-Loosening) now have killing fixtures;
scripts/mutate-cultnet-selection-fixbatch2.entries.ps1 is new, covering
Reconcile's remaining two mutants.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Collapses the rest of the Mesh selector surface named in docs/cultnet-selection-cut.md
section 4/11 onto CultNetSelection:
- CultMeshSnapshotRequestOptions becomes a record carrying Selection in place of
SchemaIds/RecordKeys; its three hand-written clone functions
(CloneSnapshotRequestOptions x2, CloneSnapshotFacadeRequestOptions) are gone in
favor of `with`.
- CultMeshPeerSnapshotDocumentOptions becomes a record for the same reason;
WithPublicationBindingOptions and ToSnapshotRequestOptions collapse to `with` and
one inlined Selection literal at their single call site.
- CleanSnapshotFilter and the three-times "no schema filter when keys are given"
rule collapse into two owners: CultMesh.ResolveDefaultSelection (the typed-document
default) and CultMesh.OverlaySelection (explicit override over defaults).
- CultMeshHotBodySubscription carries one Selection instead of parallel
RecordKeys/SchemaIds allowlists.
- ReadDocumentFromSnapshotResponse's four-tier fallback collapses to one
recordKey-anchored read: exact schema/alias match, or (foreign/runtime-generated
schema id) a payload that decodes as TDocument. The two tiers that matched by
schema alone or recordKey alone - the ones that could return a record under the
wrong key - are gone; DocumentHandle_ReadsRemotePeerSnapshotsAsTypedDocuments
pins the surviving recordKey-anchored decode fallback.
Two items in section 11's table are not cut here, by finding rather than oversight:
- IsSameCultDocumentSchema (CultMesh.cs) already delegates to the one
CultNetSchemaAliasMatching owner (no second engine); deleting the wrapper would
duplicate its type-to-descriptor resolution at four call sites instead of
collapsing it. Left in place.
- The selector literals in CultMeshClient.cs (574-604, 830-860, 932-962) call
CultNetDatabaseSubscriptionClient.SubscribeAsync, which stays a v0
(schemaIds/recordKeys) API in this cut - that client is not named in section 7's
per-file plan. There is no Selection to move these onto without adding a v1
subscribe path to Networking, which is out of this commit's scope. Left in place.
Tests: GameCult.Mesh.Tests 252/253 (1 pre-existing unrelated skip), 0 failed.
GameCult.Networking.Tests 161/161 excluding one pre-existing flaky test
(DatabaseSubscriptionServer_ReconcilesDeliveredProjectionAndBodyDemandWhenAuthorityChanges,
a background RUDP poll thread racing a disposed socket in code this commit does not
touch; it crashes the test host the same way on this branch before this commit too).
GameCult.Caching.Tests 188/188.
Net line count, src/GameCult.Mesh only: +143/-226, net -83. Section 11 estimated
about -326 for this cut's slice. The gap is mostly the two items above (about -49
of the estimate) plus one already-ruled keep this map's Progress block already
named (the foreign-schema-id decode fallback, -41 in the table, kept per the prior
Hands' ruling and untouched here) and the four-tier fallback surviving as a
two-tier one rather than a zero-fallback exact read (an honest test-enforced
requirement, not a padding choice).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
One codec, `packages/cultmesh-ts/src/realtime-wire.ts`, rule for rule what
`CultMeshRealtimeWireProtocol` does: the 37-byte little-endian fixed header
with magic 0x31545343, the delivery byte as the C# enum ordinal, the i64
epoch and sequence, three u16 identity lengths, the i32 payload length, the
stamped header size and wire version, then UTF-8 identities and the payload.
It touches no socket; QUIC stream framing stays transport-owned. The blank
reading in `Validate` is `IsNullOrWhiteSpace`, whose whitespace set is not
`String.prototype.trim`'s, so it borrows `isNullOrWhiteSpaceCSharp` from
`cultnet-ts` rather than growing a second reading of the same rule.
Byte parity is proven in both directions and in two processes, which is what
the authority vectors cost us to learn. `contracts/cultmesh/realtime-frame-vectors.json`
is written by the new C# test under CULTMESH_WRITE_VECTORS=1 and decoded and
re-encoded here; `realtime-frame-vectors.ts-written.json` is written by
`scripts/write-cultmesh-realtime-frame-vectors.mjs` with this codec, over
different identities and epochs, and decoded by the same C# test with the
reference implementation alone. Both files share one field shape, so each
side reads both with one reader. The two cases that would put megabytes into
a committed contract file, identities at exactly 65,535 UTF-8 bytes and a
64 MiB payload, are recorded as a build rule plus the digest of the whole
encoding; both sides build them the same way.
Three things the C# reference gets from its type system and TypeScript does
not, each refused rather than left to surface as a stray runtime error: a
`bigint` outside the i64 range, a delivery string the union cannot spell, and
a leading U+FEFF, which `TextDecoder` deletes by default and
`Encoding.UTF8.GetString` keeps. The last one is a real divergence the test
caught; the decoder now sets `ignoreBOM`. U+FEFF is not whitespace to .NET,
so such an identity passes the transport contract and reaches the wire.
There was no C# test for this codec before; this is also its first.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`contracts/cultmesh/authority-route-vectors.json` proves C# bytes verify
in TypeScript. Nothing in the repo proved the reverse; Soul's probe did it
once in a scratch directory. It is now a committed check in the same shape,
mirrored:
- `scripts/sign-cultmesh-authority-vectors.mjs` builds the route and
session transcripts with the shared module, signs them with fixed
TypeScript-owned test keys over awkward inputs (a quic scheme with a
query, three unsorted protocol ids, non-ASCII generation text, priority
13), self-verifies, and writes
`contracts/cultmesh/authority-route-vectors.ts-signed.json`.
- `CultMeshAuthorityProofTests.AuthorityRouteVectorsSignedByTypeScriptVerifyHere`
reads that file with keys taken from the file, asserts the protocol ids
are unsorted, that `Validate` and `VerifySessionProof` accept, and that
a priority-tampered route and a replayed nonce are refused.
- The TypeScript vector test runs over both files, so a transcript change
on the TypeScript side fails here before C# could trust a stale file.
Checked negatively: one base64 character changed in the committed
signature fails the C# test.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Cut 1 of the TypeScript QUIC realtime plane: one TypeScript
implementation of Odin root lookup, the validity window, the canonical
route and session transcripts, P-256/P1363 verification, loopback
detection and the protected-scheme rule, published as
`cultnet-ts/authority`. Browser-safe by construction: WebCrypto,
TextEncoder, atob/btoa, URL, DataView; no node: import, and a test that
says so.
The protected-scheme rule is the C# rule (wss, https, any scheme
containing "quic") rather than wss-only, so the module is not wrong for
QUIC routes; the browser client still refuses non-ws(s) endpoints itself.
The transcript bytes are pinned by contracts/cultmesh/authority-route-
vectors.json, written by the C# reference under CULTMESH_WRITE_VECTORS=1
with fixed test keys and otherwise re-verified by both runtimes. The
vector carries a route certificate and a provider session proof.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The generator and CultDocumentRegistry now discover members by the same
steps: most-derived declaration first, override chain collapsed to one
member, attributes read up the override chain, rejections in one order
with one text (GCC001 / InvalidOperationException).
- Accessors are named per document, so documents sharing a [CultName] or
[CultIndex] base member no longer emit duplicate methods (CS0111).
- Duplicate [Key] slots are refused, including new-hidden fields and
properties; a hidden persisted member with a different slot is refused.
- String [Key("x")] gets its own message instead of "has no [Key]".
- A persisted property whose set accessor is private, protected or init,
or a readonly field, is refused instead of CS0272 in generated code.
- Overrides: MessagePack's DynamicObjectResolver reads the base
declaration's [Key]/[IgnoreMember] (it throws "key is duplicated" for a
re-keyed override and serializes an override marked [IgnoreMember]).
An override that changes them is refused in both paths; an override
that repeats or omits them is accepted and byte-pinned.
- Typed Serialize<T>/Deserialize<T> used static Options and skipped
generated codecs and declared resolvers; deleted. CultNetDocumentBinding
and CultMeshSnapshots route through the untyped path with their registry.
DocumentShapeSweepTests compiles each shape through the generator and
compares generated vs reflective schema id, catalog, name/index values
and payload against MessagePack's own bytes, or equal rejection text.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ObserveAsync, ReactiveAsync and RefreshAsync read a snapshot without a
Sequence, so an older change admitted before the read but delivered after it
overwrote the newer value.
CultCache.GetWithSequence(key) returns the document and the cache's Sequence,
read together under the gate. A cache-backed CultMesh handle passes it to its
mirrors; each mirror subscribes first, then adopts the sequenced read under its
own gate and takes that Sequence as applied, so a straggler at or below it
drops. RefreshAsync on both mirrors uses the same adoption path. Public Mesh
surface unchanged. The read is non-generic because the Mesh route needs the
untyped document for same-schema conversion.
Contract: only Watch/WatchRecord changes carry Sequence; OnUpdate-derived
CultNet streams have no ordering or stale protection; schema-alias handles and
removals are not stale-protected; the sequence constructor argument changed
CultCacheDocumentChange<T>'s binary signature.
Tests: ObserveAfterNewerSnapshotIgnoresStraggler and
RefreshIgnoresStragglerOlderThanSnapshot (observed and reactive cases) fail on
4562340; SequencedReadReflectsEveryChangeAtOrBelowItsSequence.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
bcae483 made the cache a delivery scheduler (tickets, _deliveryGate turn
waits, per-thread delivery queues). It deadlocked inside CultLib: a load's
delivery reaching a CultNetDatabaseSubscriptionServer observer that needs
_lifecycleGate, while a subscribe's demand handler holding that gate writes
the cache and waits for the delivery's turn.
The cache now owns order as data. Every admitted change takes the next value
of a per-cache in-memory sequence under the gate, published as
CultCacheDocumentChange<T>.Sequence. Delivery is the 544c087/b653f68 rule:
each call publishes its own changes on its own thread after releasing the
gate; nothing waits on another call's delivery. Cross-thread delivery order is
not guaranteed.
Deleted: ticket counter, _delivered, _deliveryGate, Monitor.Wait turns,
per-thread delivery queues, per-ticket catch-all, and the save-order contract
text with its observer-must-not-block rule.
Consumer: a cache-backed CultMesh document handle hands its observed and
reactive mirrors each snapshot's Sequence; ApplyCanonicalSnapshot drops one
not newer than the last applied.
Tests: SequenceIncreasesInAdmissionOrder,
ObserverWritingTheSameCacheDoesNotWaitOnOtherDeliveries,
SubscriptionDemandHandlerWritingCacheDuringDeliveryDoesNotDeadlock,
WatchRecordIgnoresStaleSequence. The last three time out on bcae483.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cut 3 of the CultCache migration (Aetheria docs/cultcache-migration-cut.md, 2.1
and 2.2). One commit, because routing, the batch that replaces the ambient
transaction, and conditional commit share one admission path.
Deleted first:
- globals: InitializeGlobals, MaterializeMissingGlobals, the two-argument
constructor, the initializeGlobals plumbing in CultCacheMessagePack,
ContainsDurableRecord (cache and directory store)
- replication: the push-to-every-store loops and the attach-time push of
existing records
- typeof(T) as the schema owner in AddAsync/UpsertAsync and in
CultNetDatabase.PutAsync/PutPredictedAsync
- the ambient transaction: ExecuteTransactionAsync x2, CultCacheTransaction,
CommitTransaction, VisibleStoredDocuments, the AsyncLocal, the SemaphoreSlim
gate, and every ambient check
- CultCacheOpenOptions.PullOnOpen; OpenAsync is Task.FromResult(Create(...))
- DirectoryMessagePackBackingStore.ResolveLegacyUncataloguedRecordCatalog: the
catalog precommit (fe55bff) is an ancestor of the v4 page format (eea8248),
so no v4 manifest can reach it
- CultCache.DeleteAsync<T>; its one caller, CultNetDatabaseSubscriptionClient,
called it by reflection and now calls Remove(key)
Added:
- AddBackingStore(store, params Type[] homes): attach validates (clean, not
already attached, one untyped store, no claimed home type, no admitted
record changes home) and then pulls under the gate
- Home(type): most derived routed home, else the untyped store
- Admit: every add and remove, from a write, a batch or a load, validates
(home, read-only, loaded-from-home, global singleton), lands durably, then
applies and publishes; a pull hands its whole delta over in one call, so a
refused record leaves the store and the cache unchanged
- read-only stores (CacheBackingStore(readOnly), both store constructors,
CultCacheOpenOptions.ReadOnly); flush skips them and writes only dirty stores
- assignable GetGlobal<T>/GetByName<T>/GetByIndex<T>/Watch<T>; more than one
candidate throws naming them
- Commit(Action<CultCacheBatch>) returning bool, TryCommit returning
CultCommitOutcome, CultCacheBatch (Upsert, Remove, Expect, ExpectUnchanged,
sealed after commit), CultCommitRequest; CacheBackingStore.CommitBatch
evaluates conditions under the store's exclusive lock
- single-file store: <path>.lock sidecar for CommitBatch and PushAll; a clean
store commits onto the file as it is, so records it never loaded survive
- storedAt minted in one place and bumped a tick when not later than the record
it replaces
Consumer adjustments in this repo: Mesh streaming tests drop PullOnOpen; the
Studio bridge stops reflecting on it; BackingStoreTests' global test creates
its global explicitly and its transaction tests are rewritten against Commit
(the two that proved ambient hydration rules are deleted); the pre-Cut 2
refusal test expects the attach to throw.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Removes CultSoaTable, CultSoaColumn, CultCacheSoaStore, CultCacheSoaTypeTable,
CultCacheSoaMember, CultCache.Soa<T>, the _soa field and its four hooks, and
the two tests that exercised them (with their TransformEntry and
MeshManagedPlayer fixtures). The last working form is at
parked/cultcache-soa.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>