Cut the dead StoredSchemaReadWhole defaults and Wrote sets; test the refusal text, the cacheless declaration and the unnameable claimant

Stryker found the true default and the two Wrote sets equivalent: a constructed or written record is stored under its type's declared id, so the flag is never asked. Not run at this commit.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-27 05:34:43 +01:00
commit f87d12b9ae
3 changed files with 33 additions and 8 deletions

View file

@ -342,7 +342,6 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore
var indexRecord = ToContentAddressedIndexRecord(record, pagePayload);
stored.StoredVersion = VersionOf(indexRecord.Payload);
stored.StoredSchemaId = stored.Descriptor.SchemaId;
stored.StoredSchemaReadWhole = true;
currentIndex[key] = indexRecord;
WriteFileAtomically(
ContentAddressedRecordPath(indexRecord),

View file

@ -518,9 +518,10 @@ namespace GameCult.Caching
// True when the schema this record was loaded under left none of its persisted slots unread by Descriptor, so the type reads
// it whole and owns it (CultDocumentRegistry.Owns). Set by ToStoredDocument from the schema comparison's ignored slots,
// before any variant override is shed; true for a record this cache constructed or wrote. Ownership at the staging half
// asks it; ConditionsHold never reads it.
internal bool StoredSchemaReadWhole { get; set; } = true;
// before any variant override is shed. A record this cache constructed or wrote is stored under Descriptor's own id, which
// the registry declares, so only a loaded record under another id is ever asked. The staging half asks it;
// ConditionsHold never reads it.
internal bool StoredSchemaReadWhole { get; set; }
// Names the bytes this record was read from or written as: the SHA-256 of its persisted encoding, set by the store that
// read or wrote them. A record no store has read or written has a version of its own that equals nothing else.
@ -2107,7 +2108,6 @@ namespace GameCult.Caching
if (!_entries.TryGetValue(record.Key, out var entry))
continue;
entry.StoredSchemaId = record.SchemaId;
entry.StoredSchemaReadWhole = true;
entry.StoredAt = record.StoredAt;
entry.StoredVersion = version;
if (entry.Variant == null)
@ -4708,7 +4708,6 @@ namespace GameCult.Caching
if (!Entries.TryGetValue(write.Record.Key, out var entry))
continue;
entry.StoredSchemaId = write.Record.SchemaId;
entry.StoredSchemaReadWhole = true;
entry.StoredAt = write.Record.StoredAt;
entry.StoredVersion = version;
}

View file

@ -175,6 +175,7 @@ namespace GameCult.Caching.Tests
cache.Commit(batch => batch.Upsert(new NoteGrown { Name = "note", Value = 2 }, new CultRecordHandle<NoteGrown>(Note))))!;
Assert.That(upsert.RecordKey, Is.EqualTo("note"));
Assert.That(upsert.SchemaId, Is.EqualTo(WideRegistry.GetRequired<NoteWide>().SchemaId));
Assert.That(upsert.Message, Does.Contain("would drop persisted slots").And.Contain("to accept the loss."), "the refusal names the narrowed rule");
var remove = Assert.Throws<CultSchemaConflictException>(() => cache.Commit(batch => batch.Remove(Note)))!;
Assert.That(remove.RecordKey, Is.EqualTo("note"));
Assert.That(Fingerprint(path), Is.EqualTo(before), "a refused write leaves the store as it was");
@ -228,11 +229,37 @@ namespace GameCult.Caching.Tests
{
var path = SeedWide(directory: false);
var before = Fingerprint(path);
Assert.Throws<CultSchemaConflictException>(() =>
new SingleFileMessagePackBackingStore(path).WriteDocument(Note, new NoteGrown { Name = "note", Value = 2 }, registry: GrownRegistry));
var refusal = Assert.Throws<CultSchemaConflictException>(() =>
new SingleFileMessagePackBackingStore(path).WriteDocument(Note, new NoteGrown { Name = "note", Value = 2 }, registry: GrownRegistry))!;
Assert.That(refusal.Message, Does.Contain("reads it whole").And.Contain("to accept the loss of its unread slots."), "the refusal names the narrowed rule");
Assert.That(Fingerprint(path), Is.EqualTo(before));
}
// A cacheless writer's entry that lists the record's id compatible consents to the loss, as a cache's declaration does.
[Test]
public void ACachelessWriteByAnEntryListingTheIdCompatibleOverwritesALossyDriftRecord()
{
var path = SeedWide(directory: false);
new SingleFileMessagePackBackingStore(path).WriteDocument(Note, new NoteGrownDeclaring { Name = "note", Value = 2 }, registry: DeclaringRegistry);
Assert.That(StoredIdOf(path, false, DeclaringRegistry, Note), Is.EqualTo(DeclaringRegistry.GetRequired<NoteGrownDeclaring>().SchemaId));
}
// The type that would read the id cannot be named (two registered versions share the schema name and the id declares neither),
// so the record is not owned and a write over it is refused typed, not as the resolver's own error.
[TestCaseSource(nameof(StoreKinds))]
public void ARecordNoRegisteredTypeCanBeNamedForIsNotOwned(bool directory)
{
var path = SeedNarrow(directory);
var both = CultDocumentRegistry.ForTypes(new[] { typeof(NoteNarrow), typeof(NoteGrown) });
using var cache = Open(path, directory, both);
using (var other = Open(path, directory, WideRegistry))
Assert.That(other.Commit(batch => batch.Upsert(new NoteWide { Name = "note", Value = 3 }, new CultRecordHandle<NoteWide>(Note))), Is.True);
Assert.Throws<CultSchemaConflictException>(() =>
cache.Commit(batch => batch.Upsert(new NoteGrown { Name = "note", Value = 4 }, new CultRecordHandle<NoteGrown>(Note))));
}
[TestCaseSource(nameof(StoreKinds))]
public void ExpectAgreesWithRestamp(bool directory)
{