Variants C1: the held index tells the truth; a merge is judged on what the file will hold

The held index kept one key per value, last writer winning, and RemoveKey dropped
a value outright. Two consequences: removing or changing one of two plain
holders left the other unfindable, and the variant index rule judged a variant
against that lossy view, so a variant could land on a value a plain record still
held and the store then refused to reopen.

- _indexes maps a value to the keys holding it, oldest first. Index, Unindex and
  the removal maintain the set. GetByIndex finds the most recently indexed
  holder of a value, so removing the winner finds the other (behaviour change for
  plain duplicates: it used to find nothing).
- One rule, RefuseVariantIndexSharing, serves both write orders. It reads the
  truthful index minus what the commit replaces or removes, plus what it lands.
  ResolveOne no longer judges indexes; ClaimIncomingIndexValues, HeldByVariants
  and RefusePlainSharingWithVariant are gone.
- A conditional commit merged onto the file is judged by the cache's own
  resolution (CacheBackingStore.Judging) on the merged set: records other writers
  added, records they removed, and the batch. Another writer's variant sharing a
  value with the batch, or a base removal that strands another writer's variant,
  is refused with the named refusal instead of writing an unopenable store.
- Shedding is one decision at load. ToStoredDocument resolves the schema once and
  returns the report with it; a variant's override of a slot the type lacks is
  dropped from the delta there, so flush needs no predicate and every store
  reports through the same surface (ReportVariantSheds and OverridesTheTypeHas
  are gone). The directory store refuses variants and loads through the same call.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-29 09:20:47 +02:00
commit 282e337c2c
3 changed files with 329 additions and 144 deletions

View file

@ -339,11 +339,11 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore
var started = tracePages ? Stopwatch.GetTimestamp() : 0L;
var metadata = records[index];
var record = ReadPersistedRecordPage(metadata, out var pagePayload);
recordReports[index] = Registry.ResolvePersistedSchemaReport(record.SchemaId, catalogEntries);
storedRecords[index] = ToStoredDocument(
record,
catalogEntries,
(type, payload) => CultDocumentMessagePackSerialization.DeserializeUntyped(type, payload, Registry));
(type, payload) => CultDocumentMessagePackSerialization.DeserializeUntyped(type, payload, Registry),
out recordReports[index]);
if (tracePages)
{
pageBytes[index] = pagePayload.LongLength;