Fix batch 4 (S-9/S-11): shard-scoped asOf, and the v1 subscribe door runs before the hop refusal
S-9: CurrentAsOf() returned the maximum shard-log sequence across every shard the database holds, but that sequence is a per-shard counter - mixing shards' counters together could report a page as exact "as of" a sequence a different, untouched shard advanced to while the page's own shard sat still. CultNetDatabase gains CurrentAsOf(shardId), the watermark of one shard's own log, and CultNetDocumentRegistry.SelectPage now scopes asOf to the single shard EnsureSingleShard already resolves the matched rows to, falling back to the caller-supplied value only when the selection matches no rows. S-11: CultNetDatabaseServer.HandleSubscribeV1Async refused a hop-bearing selection before running the door (Validate), so a selection invalid for an unrelated reason (an empty keys list, an undeclared index, ...) got a hop refusal that had nothing to do with why it was actually rejected. The door now runs first, unconditionally. The hop refusal also always named the field "cites" even when the selection carried Cited instead; it now names whichever hop is actually set. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
e99bf46520
commit
b3a8f6d195
4 changed files with 115 additions and 25 deletions
|
|
@ -1303,14 +1303,31 @@ namespace GameCult.Networking
|
|||
_lastWriteSequence.TryGetValue((schemaId, key.Value), out var sequence) ? sequence : null;
|
||||
|
||||
/// <summary>
|
||||
/// The current write-sequence watermark this database can present an exact snapshot for (R-A):
|
||||
/// the highest <see cref="LastWriteSequence"/> assigned to any row, 0 when nothing has been
|
||||
/// committed yet. This is the evaluator's <c>asOf</c> for a v1 snapshot or subscription page - it
|
||||
/// advances on every committed write, so a cursor minted against an older watermark refuses as
|
||||
/// <c>cursor_stale</c> once a page's answer could differ.
|
||||
/// The current write-sequence watermark across every shard this database holds, 0 when nothing
|
||||
/// has been committed yet. Only meaningful for an answer that is not scoped to any particular
|
||||
/// shard's log - a selection's own <c>asOf</c> must use <see cref="CurrentAsOf(string)"/> instead
|
||||
/// (S-9): the shard-log sequence is a per-shard counter (<see cref="NextMutationLogSequence"/>),
|
||||
/// so taking the maximum across every shard's rows mixes counters that do not compare to one
|
||||
/// another and can report a page as exact "as of" a sequence another, unrelated shard advanced to
|
||||
/// while the page's own shard sat still.
|
||||
/// </summary>
|
||||
public ulong CurrentAsOf() => _lastWriteSequence.Count == 0 ? 0UL : (ulong)_lastWriteSequence.Values.Max();
|
||||
|
||||
/// <summary>
|
||||
/// The write-sequence watermark of one shard's own log (S-9): the sequence of the last commit
|
||||
/// that shard's log recorded, or 0 when the shard has never taken a write. This is the evaluator's
|
||||
/// <c>asOf</c> for a v1 snapshot or subscription page whose matched rows all come from one shard
|
||||
/// (<see cref="CultNetDocumentRegistry"/>'s single-shard check) - it advances only when that
|
||||
/// shard's own log advances, so a cursor minted against it refuses <c>cursor_stale</c> exactly
|
||||
/// when that shard's log has moved, never because an unrelated shard elsewhere took a write the
|
||||
/// selection never touched.
|
||||
/// </summary>
|
||||
public ulong CurrentAsOf(string shardId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(shardId)) throw new ArgumentException("Value must be non-empty.", nameof(shardId));
|
||||
return _nextLogSequences.TryGetValue(shardId, out var next) ? (ulong)(next - 1) : 0UL;
|
||||
}
|
||||
|
||||
private void RecordMutationLogEntry(
|
||||
CultNetShardMutationLogEntry entry,
|
||||
CultNetShardLogEntryMessage? wireEntry = null)
|
||||
|
|
|
|||
Loading…
Reference in a new issue