The ForeignRecordTests read-only fixtures (another runtime's deck, the moving deck) had the same shape as the deck, which the
type now reads whole and owns; each gets a slot the deck lacks, so it is read only by dropping it. RestampOnWriteTests and the
successful-churn gate test are corrected from the first run. Not run at this commit.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A non-home store (single-file and directory) is read only once the commit holds its own lock, so a foreign record landed at a checked key
while the commit waits is refused; and a directory judge whose lease is held makes TryCommit Contended at once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ARefusalLeavesStagedWhatItDidNotRefuse re-staged the refused record and
expected the flush to refuse it; since b636a69f lists it foreign, that
re-staging is refused at once. The partial case now stages two records before
another writer makes one foreign.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A removal of a key the cache does not hold writes nothing, so it never reached
the apply; it was checked against the foreign list of the last load, and a
foreign record that arrived since was reported removed. Each store now reads
its durable state for such a removal (RefuseRemovalOfUndeclared) and refuses
through the apply's own RefuseUndeclaredOnDisk, which reloads and lists the
record. The load-time check (RefuseRemovalOfForeign) is deleted: the durable
read refuses everything it refused, and it could not save that read. A store
kind that cannot read its durable state keeps the view check as its default.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Refuse reloads a refused key as a pull loads it, and now that includes the
store's foreign list: a record another writer moved to an id this cache cannot
read is listed in ForeignRecords after the refusal, so the next write of that
key is refused at staging instead of again at the flush. Both store kinds give
Refuse the durable record's foreign entry (ForeignAt); the variant refusal too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The persistence contract's rule 3 now says the store refuses, under its lock, a
write or removal at a key whose durable record is under an undeclared id, for
the store's own write API and for CultMesh single-file writes, which declare
through the catalog entry they write. The sentence that a declaring type stores
the record under its own id is corrected: Declares is registry-wide, so the
record lands under the writing type's id. The late-registration test is renamed
to match.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A record another writer moved to an undeclared id after the load, a staged
removal of one, and one that appeared at a written key are refused at the apply
and reloaded read-only. A direct store Push, Delete and CommitBatch of a
read-only record are refused and never relabel it. Removing a foreign record is
refused typed. A CultMesh write over a record its entry does not declare is
refused, and one whose entry lists the id lands under its own id.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The held-only mutant of RefuseUndeclared survived the suite, because the
apply-time refusal still caught the write at flush. This pins the staging
half for foreign records in both store kinds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The relabel tests become refusals: ARecordReadByNameIsReadOnly (name branch)
and AnOlderVersionCannotWriteANewerRecordItReadsThroughTheNewerEntry (a
persisted entry's list). New: every write path refuses before staging,
declaring the id claims the record, a type registered after load claims it,
MintElementIds is refused by a read-only record, the undeclared dropped-slot
case, and a variant of a read-only base lands. The copy test also checks the
write beside the read-only record landed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The foreign-record header test expected a marked header to drop while a record was copied
forward, which the header rule no longer does. A directory store loads a record from its
page, not the manifest, so its skew test makes both writers hold the record ahead instead of
editing the manifest.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
New WriteSetTests (single-file and directory stores, flush and commit): another writer's write and
removal survive, a store that appeared after open is kept, an unstaged in-place change is not
written, a copied record keeps the catalog entry the store holds now, an entry's unknown trailing
fields survive byte for byte, conditional and unconditional commits write the same bytes, the
header drops only when every copied record is known id-free, v2 exactly with a variant. Mesh: a
write copies neighbours forward and replaces only its key. The lay-back and whole-view tests and
the flush-restamp tests are rewritten or removed with the rules they pinned.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
_unowned with its rebuild, Owns, Publisher, WholeViewWrite, WholeView, SameStoredBytes and
WroteWholeView are deleted before the write-set apply that replaces them; PushAll and
CommitBatchCore still call them, so this commit does not build. The contract states the
write-set rule that the next commit implements.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A pull runs only on a clean store, and a store is clean exactly when nothing is staged, so the pull's clear was redundant and is gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CacheBackingStore.RefuseForeign now decides, for both store kinds, which written keys a foreign record refuses, drops the
store's staged change and the cache's copy of each through the Loaded path a pull uses, and throws one conflict naming every
refused key. The single-file store tracks the keys staged since its last load or write, as the directory store already did, so
a clean copy of a record another writer made foreign is carried, not refused.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The file's record moves when only its storedAt or only its schema id differs from the loaded one; a
staged removal is refused and dropped with the rest; a refusal leaves staged what it did not refuse.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WriteGeneration left the refused key in the dirty and deleted sets, so every later generation
refused it. The refusal is now one check before any staging, shared by flush and commit, that
removes the refused keys from the pending sets. Tests cover both store kinds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The lay-back fixture gains an older entry listing the id ahead of the
one that owns it, and a test where another writer re-describes the id
after the load: a whole view (flush and commit) lays back the entry it
loaded. Kills the survivors in Publisher's own-id preference, the
laid-back entry's precedence in the written catalog, and the commit's
choice of that catalog.
- A store whose file is gone lists no foreign records.
- The registry's public resolution still refuses an unclaimed id, naming
the schema and the id (was NoCoverage).
- A directory load retried after a writer's commit lists each foreign
record once.
- Deleted LaidBack.ExceptWith(replaced): equivalent, since an upserted key's
new entry already carries what the cache would be told.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
C2a F2, widened by Self: records and catalog entries a runtime does not own
survive its writes byte for byte. The schema-identity probe showed a C#
rewrite restamping every other runtime's record to its sha256 id and
dropping their catalog entries.
- The single-file store remembers, per key, a record it loaded under an id
its type neither owns nor declares compatible (resolved through the
catalog's schema name or an entry pointing at a local id), with the entry
that published it. While the store still holds the entry it loaded, a
whole-view write (flush or unconditional commit) lays that record back
with its id, storedAt, bytes and catalog entry. A write of the record
itself still stores it under the type's own id.
- A record published by an entry under the type's own id is this schema's
and is re-encoded as before (the registered entry wins its own id).
- A laid-back record's load-minted element ids stay in memory only, so
MintElementIds still rewrites it.
- The rolling-deploy tests lose their premise: an older version's whole
view no longer sheds a newer record's member. They now pin that it lays
the record back and the newer cache's condition holds; the A-B-A test
keeps F6's defence through an explicit older write.
- Contract: the stamp and the lay-back rule in the persistence format.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
C2a F2, operator: "CultCache never destroys or relabels a record it doesn't
understand; keeps it byte-for-byte, refuses writes that would overwrite it."
- The registry's claim decision (Claimant) is split out of resolution. It
never decodes or compares shapes, so a store classifies a record from its
header. The last branch, "No local CultCache schema matches", now means
foreign instead of a refused load.
- Both store kinds keep a foreign record out of the cache and list it in
CacheBackingStore.ForeignRecords (key, schema id, catalog name, storedAt).
- Single-file store: a whole-view write lays back every foreign record the
file holds now, with its catalog entry, and keeps a marked header while it
carries one (it cannot see their ids). A staged write, a commit (onto the
file or whole view) and a whole view over a key the file holds as foreign
refuse with CultSchemaConflictException naming the key and id. A merge
judgement never decodes a foreign record, and one that replaced a held
record takes it away.
- Directory store: a foreign record's page is never read; a generation that
would replace or remove one refuses before any page is written.
- Contracts: hard rejection no longer lists "no compatible local schema
candidate"; the persistence format says what a foreign record is.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>