Cut 2 deleted the stage probes and the tests that injected faults through them.
DirectoryStoreDurabilityTests takes the faults from held files instead:
FailedManifestReplaceKeepsPreviousGeneration,
SameStoredAtDifferentPayloadWritesADistinctPage, PullAllWaitsForHeldCommitLease,
MutationBlockedBehindFlushStaysDirty, FlushNeverOpensUnchangedPages,
MissingManifestIgnoresOrphanPagesAndFlushDeletesThem,
TamperedPageFailsLoadNamingPage.
The tampered page surfaces as an AggregateException from the parallel page
load whose message names the page; the test asserts the page is named.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ConditionalCommitTests: StaleExpectFailsWithNothingChanged, ExpectNullIsCreateOnce,
PerEntryCommitSurvivesUnrelatedWrite, ExpectUnchangedFailsOnUnrelatedInsert,
TwoWritersRacingExactlyOneWins, TryCommitReportsContended,
PlainFlushNeverInterleaves, StoredAtIsStrictlyIncreasingPerKey,
DirtyStoreRefusesConditionalCommit, CrossStoreConditionalBatchThrowsBeforeAnyStore,
DirectoryStoreHonorsConditions (the four condition tests on a directory store).
The race is in-process: two store instances on one file, two threads, the
final count exactly 2 + 2N. The lock is a FileShare.None handle, which excludes
a second handle in this process exactly as in another, so the cross-process
TwoProcessesRacingExactlyOneWins and its console project are not added. Each
writer's mismatch is scripted before the free race, so "each writer reports at
least one mismatch" does not depend on scheduling.
PlainFlushNeverInterleaves counts an open that loses to a replace in flight as
no read; every read that gets bytes must deserialize.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cut 1's fixtures are rewritten as the cut map names: LoadingNeverWritesASecondStore
attaches its second store with a home type, and the global fixture is a real
[CultGlobal] type instead of a reflected backing field.
New: RoutedStoresWriteTheSameBytesAsSingleStores, CatalogRecordNeverLandsInRunStore,
AttachAfterDirtyThrows, SecondUntypedStoreThrows, DuplicateHomeTypeThrows,
WriteWithoutHomeThrows, ForeignRecordIsRefusedOnLoad,
LateRouteOverAdmittedTypeThrows, ReadOnlyStoreRefusesWrites,
ReadOnlyStoreIsNotFlushed, GlobalSingletonIsEnforced,
AmbiguousAssignableLookupThrows, BatchIsAllOrNothingOnStoreFailure,
BatchAcrossTwoHomesThrows, BatchObserversSeeOnlyCommittedRecords,
BatchReadsSeeCommittedStateOnly, SingleUpsertAndBatchShareOneAdmissionPath,
PullAllAdmissionFailureLeavesStoreAndCacheConsistent,
StoreAttachedToSecondCacheThrows.
Two readings where the map could not be followed literally:
- LateRouteOverAdmittedTypeThrows: "the reverse order succeeds" cannot hold
with the same untyped file holding a Note, because a Note loaded from the
untyped store when a Note route exists is a foreign record. The reverse case
puts the Note in the routed store and a RoutingOther in the untyped one.
- BatchIsAllOrNothingOnStoreFailure: the fault is the store file held open
without delete sharing, so the atomic replace fails, instead of a PushAll
override; the single-file commit no longer routes through PushAll.
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>
REC-032 commit 1: move the canonical 33 TS + witness-artifact-bundle
schema files (34 total) into runtime-neutral CultLib/contracts/cultnet,
move the interop-note schema (and delete the zero-consumer discovery-*
schemas per D3, tagged parked/cultnet-discovery-contracts) into
contracts/cultnet/interop, and delete the 27 C# and 24 Rust duplicate
copies after confirming byte-for-byte equivalence (only
cultnet.snapshot-request differs: TS carries shardId/shardEpoch, a
known drift already resolved in favor of the TS shape).
Rewrite every $id from https://github.com/GameCult/cultnet-ts/contracts/
and .../cultnet-ts/integration/contracts/ to
https://github.com/GameCult/CultLib/contracts/cultnet/(interop/); give
the two shard-catalog schemas URL ids per D1, replacing the bare
cultnet.shard_catalog_*.v0 identifiers; leave ghostlight.agent-state's
Ghostlight-owned $id untouched per D5.
Repoint every runtime's load path and id literal at the new location:
C# csproj EmbeddedResource + LogicalName, Rust include_str! paths,
TS contracts.ts imports + swarm-contracts manifest/tooling + package.json
files/prepack, and the id/path literals in the C#, Rust, Python and
Kotlin interop tests and peers. Python and Kotlin still build their own
schema JSON in-process (D2); only their base constants, id literals,
and (for Kotlin) contentHash literals were swapped. Every Kotlin
contentHash was recomputed from the final canonical files using the
runtime's own stable-stringify + SHA-256 function; all matched the
planner's precomputed values except cultnet.schema-catalog-response,
where the planner's proposed value did not match recomputation and the
freshly computed value was used instead.
REC-032, decisions D1/D2/D3/D5.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The repo-wide text=auto with core.autocrlf=true would rewrite an LF byte in
a content-addressed page on checkout and break its SHA-256 identity.
Committed blobs already match the bytes produced at 0db1fe5.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fixtures were produced by CultLib 0db1fe5 from a temporary worktree: a v4
directory store and a v1 single-file store holding the same three records.
Tests reopen both and assert every value, and assert the directory store
refuses v1, v1.directory, v2 and v3 manifests without touching the bytes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SchemaStampedTestEntry was only used by the v1 missing-catalog load test
deleted with the legacy directory formats. The Caching tests README now
describes what the suite covers after the subtraction, including the ambient
transaction tests kept until Cut 3 and the red store-routing contract tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SerializeSchemaCatalog and DeserializeSchemaCatalog had no consumer outside
the snapshot round-trip test. Catalog entries still serialize inside
SerializeSnapshot/DeserializeSnapshot through the same per-entry
reader and writer, and the round-trip test now checks the catalog through the
snapshot.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
DirectoryMessagePackBackingStore loses HydrationFilter, FlushStageProbe,
ReadStageProbe and their stage constants, ReadPersistedGeneration,
PullSelected, and every legacy path: the v2 directory-indexed and v3
immutable-pages formats, v1 inline records and split pages, the index
upgrade on flush, and the legacy page paths. CacheBackingStore.PullSelected
and CultPersistedRecordMetadata go with them.
A missing manifest is an empty store. An existing manifest whose
FormatVersion is not cultcache.store.v4.directory-content-addressed-pages
throws "Directory store {path} is {format}; only {v4} is readable." A clean
flush writes nothing, since there is no legacy index left to upgrade.
Tests deleted with the surface they injected through: the v1 missing-catalog
load, pages-before-manifest, fault-before-manifest (4 cases), same-timestamp
page, mutation-during-flush and reader-lease tests. The orphan-page test now
checks through a fresh store pull instead of ReadPersistedGeneration.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ConfigureCache, ConfigureStore, DirectoryStorePath, DirectoryStoreHydrationFilter
and ConfigureDirectoryStore had no consumer outside tests. CreateCore attaches
the directory store at its default record path, or the single-file store,
with no customization hooks. OpenAsync keeps its behavior (pull, then
materialize missing globals); PullOnOpen stays until Cut 3.
The indexed-filter test loses its selective-open half and keeps the v4
manifest assertions: the hot index carries only the SHA-256 page identity and
both records reload.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Removes FlushBackingStore, both PrepareForReloadOrShutdown overloads,
PullBackingStoreRecordsAsync, TryGet<T>, TryGetByName, TryGetByIndex,
Resolve<T>, the cache and store Logger, and LastSuccessfulFlushAtUtc on the
cache and stores. GetStoredDocuments<T> folds into GetAll<T>.
The soft flag goes from FlushAllBackingStores, ExecuteTransactionAsync,
CommitBatch and PushAll. It stays on FlushAsync(bool) because
CultCacheStudioWindow reflects on that signature until Cut 6.
CultMesh.FlushAsync and CultNetLocal.FlushAsync keep their parameter and
stop forwarding it.
The store's EntryAdded/EntryUpdated/EntryDeleted subjects become
protected internal Loaded/Unloaded callbacks set by the cache at attach.
AddStoredDocumentInternal is synchronous, so the GetAwaiter().GetResult()
bridges on the store subscriptions and the global materializer are gone.
Tests: drop the LastSuccessfulFlushAtUtc asserts; OpenAsync and GetAll tests
read through Get/GetAll; the persisted-global test proves the durable global
loads without staging a default instead of going through selective hydration;
the lease-before-observers test observes through CultCache.Watch; the
PullSelected selective-hydration test is deleted.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ExecuteTransactionAsync (both overloads), RequireTransactionsForAuthoritativeWrites,
EnsureAuthoritativeTransaction and the AfterCommit deferral had no consumer
outside CultNetDatabase_RequiredTransaction_DefersRawPutPublicationUntilCommit,
deleted with them. Writes publish immediately, as they already did outside a
transaction. The cache's own transaction machinery stays until Cut 3 replaces
it with the explicit batch commit.
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>
Conditional commit, not plain flush, is what protects against unseen data.
C# batch and conditional commit are specified, not implemented. Home-type
claims use exact equality. TypeScript attach does not read. CultMath has no
encoding. Reflective and generated descriptors differ until Cut 4. Routing
tests now check the inherited Name, the global flag precondition, and that a
refused write publishes no change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Eight tests against today's API, each red for the behavior the store
composition contract removes: attach pushes into a second store, the generic
parameter chooses schema, exact-type name/global/watch lookups, invented
globals destroying a seeded file, and a refused push leaving the document in
memory.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deletes four confirmed-dead tracked files:
- CultLib.txt, src/GameCult.Caching/GameCult.Caching.txt,
src/GameCult.Unity/Assets/UI/UI.txt: concatenated snapshot copies of live
source, carrying stale E: drive provenance and obsolete dependency
versions. Live source under src/ remains the owner.
- tests/GameCult.Networking.Tests/lcov.info: tracked coverage output.
.vscode/tasks.json:14 still generates it fresh via
"/p:CoverletOutput=./lcov.info" on every coverage run, so tracking the
stale committed copy in git is the liability, not the generation step.
- .voidbot/status/void-memory-maintenance.json: a one-time "starting"
receipt (added whole in 4813570, never touched again) naming a dead
E:\Projects\CultLib path and an unimplemented repo_face_heartbeat
launcher. VoidBot's actual heartbeat state lives at
.voidbot/status/repo-face-heartbeats.{cc,json} (see
packages/config/src/index.ts REPO_FACE_HEARTBEAT_STATE_PATH) — a
different file entirely; nothing reads this one.
Kill attempts:
- git grep -n for each of "CultLib\.txt", "GameCult\.Caching\.txt",
"UI\.txt", "lcov\.info" across origin/main, excluding each file's own
path: only .voidbot birth/distiller snapshots (auto-generated onboarding
file-listing catalogs, not functional readers) and the one real lcov.info
producer in .vscode/tasks.json.
- rg across all of F:/Projects (excluding target/node_modules/.git):
gamecult-site's docs/research/gamecult-org-repo-ledger.json lists
"CultLib.txt" as a filename in a static historical repo-file inventory
(not a functional consumer); Aetheria/docs/cultcache-migration-cut.md
cites GameCult.Caching.txt:157 as an evidence line number in a
migration-history note (stale citation after this cut, not a functional
break — flagged for the record, not fixed here since that doc is out of
scope).
- voidbot MCP search for void-memory-maintenance.json / repo_face_heartbeat
found only the unrelated repo-face-heartbeats state path, confirming no
reader.
Collision check: git diff --stat origin/main against each of the other 4
CultLib worktrees (codex/recover-aetheria-authority,
codex/delvehold-isosurface, claude/media-stream-catalog, and the two
detached-HEAD worktrees, both confirmed ancestors of origin/main) over
these five paths: all empty, no in-flight work touches them.
REC-102
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>