diff --git a/research/cultnet-distributed-database/summary.md b/research/cultnet-distributed-database/summary.md
index 449c12a0..417b56ef 100644
--- a/research/cultnet-distributed-database/summary.md
+++ b/research/cultnet-distributed-database/summary.md
@@ -164,15 +164,16 @@ Shard catalog exchange now exists:
- injectable non-primary write forwarding policy
- concrete schema-v0 write forwarder for `cultnet://host:port` primary
endpoints
+- in-memory per-shard mutation logs and catch-up by last seen sequence
## Next Cut
-Build per-shard replica logs:
+Build wire-level replica log catch-up:
-- assign sequence numbers to accepted shard mutations
-- stream log entries to replicas and subscribers
-- compact logs into snapshots
-- make missed entries trigger explicit resync
+- `cultnet.shard_log_request.v0`
+- `cultnet.shard_log_response.v0`
+- raw put/delete representation for log entries
+- explicit resync-required failure when a replica asks for compacted history
That is the next foundation needed before membership, Raft-style failover, or
CRDT policy work can be honest.
diff --git a/src/GameCult.Networking/Contracts/cultnet-distributed-database.md b/src/GameCult.Networking/Contracts/cultnet-distributed-database.md
index 1cdc7016..b99b8f70 100644
--- a/src/GameCult.Networking/Contracts/cultnet-distributed-database.md
+++ b/src/GameCult.Networking/Contracts/cultnet-distributed-database.md
@@ -47,12 +47,16 @@ Implemented:
- injectable non-primary write forwarding policy
- schema-v0 write forwarder that dials advertised `cultnet://host:port`
primary endpoints
+- in-memory per-shard mutation logs with sequence numbers, epochs, mutation
+ kind, schema id, key, and document references
+- mutation-log catch-up by last seen sequence
Not implemented yet:
- membership/failure detection
- leader election or automatic shard failover
-- replicated log
+- wire-level replica log request/response messages
+- durable log persistence and compaction
- declared CRDT merge policies
## Live Invariants
@@ -69,13 +73,15 @@ Not implemented yet:
## Next Coherent Slice
-Add per-shard replica logs:
+Add wire-level replica log catch-up:
-- assign monotonically increasing sequence numbers to accepted shard mutations
-- stream log entries to replicas and subscribers
-- compact log entries into CultCache snapshots
-- reject or resync replicas that miss entries
+- `cultnet.shard_log_request.v0`
+- `cultnet.shard_log_response.v0`
+- serialize accepted mutations as raw put/delete records
+- let replicas ask for entries after a known sequence
+- return an explicit resync-required error when the requested sequence has
+ already been compacted away
-Forwarding now has both a policy seam and a concrete schema-v0 dialer. The next
-useful layer is ordered replication. Membership and leader election should wait
-until the replication log shape is boring.
+The in-memory log shape now exists. The next useful layer is moving that log
+over the wire. Membership and leader election should wait until replication
+catch-up is boring.
diff --git a/src/GameCult.Networking/CultNetDatabase.cs b/src/GameCult.Networking/CultNetDatabase.cs
index f4bbdd52..8887bf65 100644
--- a/src/GameCult.Networking/CultNetDatabase.cs
+++ b/src/GameCult.Networking/CultNetDatabase.cs
@@ -229,6 +229,74 @@ namespace GameCult.Networking
public string? Message { get; }
}
+ ///
+ /// One ordered mutation accepted for a shard.
+ ///
+ public sealed class CultNetShardMutationLogEntry
+ {
+ ///
+ /// Creates a shard mutation log entry.
+ ///
+ public CultNetShardMutationLogEntry(
+ string shardId,
+ long shardEpoch,
+ long sequence,
+ string committedAt,
+ CultNetDatabaseChangeKind kind,
+ string schemaId,
+ CultRecordKey key,
+ object? document,
+ object? previousDocument)
+ {
+ ShardId = shardId;
+ ShardEpoch = shardEpoch;
+ Sequence = sequence;
+ CommittedAt = committedAt;
+ Kind = kind;
+ SchemaId = schemaId;
+ Key = key;
+ Document = document;
+ PreviousDocument = previousDocument;
+ }
+
+ ///
+ /// Gets the shard id.
+ ///
+ public string ShardId { get; }
+ ///
+ /// Gets the shard epoch.
+ ///
+ public long ShardEpoch { get; }
+ ///
+ /// Gets the per-shard mutation sequence.
+ ///
+ public long Sequence { get; }
+ ///
+ /// Gets the commit timestamp.
+ ///
+ public string CommittedAt { get; }
+ ///
+ /// Gets the mutation kind.
+ ///
+ public CultNetDatabaseChangeKind Kind { get; }
+ ///
+ /// Gets the schema id.
+ ///
+ public string SchemaId { get; }
+ ///
+ /// Gets the record key.
+ ///
+ public CultRecordKey Key { get; }
+ ///
+ /// Gets the current document, when present.
+ ///
+ public object? Document { get; }
+ ///
+ /// Gets the previous document, when present.
+ ///
+ public object? PreviousDocument { get; }
+ }
+
///
/// Raised when a database write targets a shard this node does not own.
///
@@ -261,6 +329,9 @@ namespace GameCult.Networking
private readonly CultCache _cache;
private readonly CultNetDocumentRegistry _documents;
private readonly List _shards;
+ private readonly Dictionary> _mutationLogs =
+ new(StringComparer.Ordinal);
+ private readonly Dictionary _nextLogSequences = new(StringComparer.Ordinal);
private readonly Subject