Delete StagedOperation.Replaces and Admit's replaced set. CultCacheBatch records the keys Remove named; Land evicts the held record at each (or checks the key when held by no one) and the add lands after it, so watchers, the globals copy, the variant guard, the key-home check, Claim, the store judges and the change journal see what they see when the two are staged apart. A Flatten after a Remove in the batch is refused as a flatten of a non-variant.
CacheBackingStore gains one protected RefuseUndeclaredOnDisk overload taking the catalog, the held record, the written entry and the removed keys; both shipped stores call it. The old overload and Undeclared stay.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The probe (a throw where a loaded record is relabelled in place) tripped 32 existing tests, so
the guard is reachable, not dead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A removal followed by an add at the same key is a retype (variants:ruling:cache-refuses-retype):
the batch's staged add records that it replaces, the key is judged as the removal it names, and
the record lands. The store carries the same fact for removals the cache staged apart (Remove then
UpsertAsync), so the flush no longer reads the add as an upsert over the record it removed and
discards it. A cache with no backing store judges the upsert against the record it holds. The
variant plan's rebase-type check is deleted: write ownership refuses that retype first. The
StoredPublishedBy staleness guard is deleted (a loaded record is never relabelled in place).
Protected RefuseUndeclaredOnDisk doc states the per-key rule.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An upsert may replace the record at its key only when the type it writes there owns the record
(CultDocumentRegistry.WrittenOwns): the entry declares the stored id, or reads the stored entry whole
under its own schema name. Cache and cacheless writers, single-file and directory stores ask the same
per-key judge (CacheBackingStore.UndeclaredAt); a cache no longer replaces another registered type's
record. Registry.Owns is the removal/check rule only. Deleted: CatalogEntryLookup's registry half,
CultDocumentRegistry.Lookup, Owns<T>'s mayClaim, the per-batch union and the per-id memo.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Merges hands/garden-path-remote-write-authority (51ca81a1..1e73e206).
cultmesh-garden-path:cut_report:cut-remote-write-authority.h1/h2; Soul s1 and s2 hold.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A real LiteNetLib pair on ephemeral loopback ports carries the handlers' answers to a client that decodes them with the
production schema decoder; no fixed port and no global frame provider. The delete under a schema alias and a null
decision are pinned where each is decided.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mutation triage: removing the refusal exception's constructor body survived
because the handler threw on the uninitialized peer either way; the helper now
requires the failure to come from the peer's send, not from building the message.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Captured-bytes case in cultnet-rs error_message.rs, asserted byte-for-byte by
the C# schema contract test; the cultnet-distributed-database contract and the
Networking README state the remote write authority invariant.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CultNetDatabaseServerOptions.RemoteWriteAuthority is asked once per received
write, after the schema resolves and before the shard does; none configured
refuses every remote write; an authority that throws refuses and is logged.
A refusal applies, publishes, logs and forwards nothing. The host's own
CultNetDatabase.ApplyPutAsync/ApplyDeleteAsync ask no remote authority.
Existing server tests whose subject is not the grant pass a test-local
granting authority.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A second public door named like the peer path with no caller besides the
handlers. Hosts keep CultNetDatabase.ApplyPutAsync/ApplyDeleteAsync.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every fork the model page waited on is ruled, so its cells now carry the
rulings. GP1 confirms at runtime that the database server applies a put
from a peer that carries no identity. F13-F17 record what the remote
write authority cut stands on: peers have no identity, refusal codes are
a coordinated cross-runtime enum, forwarding re-sends as a new client,
the door has no consumer outside CultLib, and the Rust document server
has the same gap. The cut sequence splits authority collapse into its
remaining lines.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Probed at b7a68884: only the C# cache arm retypes a key (cache {A,B}
replaces B's record with A); cacheless writes already refuse. TS, Rust
and Python key records by (type, key), so two types coexist at one key
and no leaf has a retype path; leaf parity needs key identity first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The package ships MessagePackAssumedFormattable(typeof(CultRecordRef<>)) as an
MSBuild AssemblyAttribute in buildTransitive, so generated resolvers defer the
ref to CultDocumentResolver. Geometry imports the targets and drops its three
per-member [MessagePackFormatter] workarounds. GeneratorConsumer is the
generator-on twin of ConsumerBuild.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merges hands/variants-c2a at 7605bd17. Verified at 463714c4 (code tree
identical): every C#, TS, Rust and Python suite green on Yggdrasil and the
old-reader refusal gate refusing in all four runtimes
(variants:cut_report:cut-land-c2a-main.h1, Soul variants:verdict:cut-land-c2a-main.s1).
No tag: tags, Unity plugin rebuild and the declared-range gate are the 3.0.0 release cut.
Non-building branch commits exist; bisect with --first-parent.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The CHANGELOG bullet, the persistence-contract paragraph and the registry param docs of WriteRecord and WriteDocument now list the two ways a record is owned and every refusal the code makes, including a stored entry that lists another registered type's id ahead of the written one's and a call that writes no entry. The registry-less Shared read is described as it is today.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A cacheless call that writes nothing declares nothing and no written entry can claim the record, so the removal is refused and the file left as it was. Red under the Any-to-All mutant of the declared predicate in OwnsOnDisk, which owns every id when no entry is written.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The lent-shape test omitted the old id from the passed entry, so the base code refused it for the id and not for the shape. It now lists them, making the registry's wider entry the only thing that could own the record.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CatalogEntryLookup now takes the lent registry and answers from the written entries plus the registry's other types (descriptors whose schema id no written entry has). The registry Owns overload, the subset predicate and OwnsOnDisk's lent branch are deleted; the passed entry alone describes the written type, so a lent registry only adds claimants and can only refuse more.
Not yet built or run (verified by the job that follows).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The console logger repeated each line, so a raw count was 28 for 14 refusals. Names now prints the source (committed or live) and the gate counts distinct 'OLD-READER <runtime> refused <label>' lines.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The console logger repeats every test's output in its summary, so the log held 28 lines for 14 refusals.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A case that returned or passed early is Passed but prints no 'OLD-READER <runtime> refused' line. Each stage now requires the pinned number of those lines (14 C#, 3 Rust, 4 Python, 3 TypeScript) and fails '<stage>-refused-count' otherwise.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>