F1: RecordUnappliedEntry keeps the primary's wire entry on the in-memory log entry (internal Relayed) and ToLogEntryMessage returns it through the same normalisation the store uses, so a replica with no store no longer relays a skipped put as a delete; memory and disk relay identically. Soul's 4d probes are committed (chained replica keeps the row, key-prefixed reads serve only owned keys, primary-all serves everything; the two measurements are explicit) with a memory/disk relay-parity test. Rust: captured-bytes cases for unowned_schema and variant_unsupported (with and without a subscription). The error-code set is stated as a closed enum in the schema description (both copies) and in cultnet-distributed-database.md: adding a code is one coordinated change across runtimes, since Rust and TypeScript refuse an unknown code. The Soul 4c probe file's header says what it guards. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
183 lines
9.7 KiB
Markdown
183 lines
9.7 KiB
Markdown
# CultNet Distributed Database Notes
|
|
|
|
CultNet's distributed database path is a typed realtime database over CultCache
|
|
documents. The design should borrow proven distributed-systems ideas without
|
|
copying their entire machine or laundering vague consistency through impressive
|
|
words.
|
|
|
|
## Prior Art To Keep
|
|
|
|
- RethinkDB changefeeds: subscriptions should emit old/new document views and
|
|
support point, table/schema, and filtered watches.
|
|
- Firebase Realtime Database: client ergonomics should make realtime sync feel
|
|
like the default path, but presence, disconnect handling, and server-side
|
|
authority still need explicit ownership.
|
|
- Dynamo: partitioning, replication metadata, vector-ish causality, hinted
|
|
recovery, and application-visible conflict resolution are useful. "Always
|
|
writeable" is not the first CultNet promise.
|
|
- Raft: primary authority per shard is the first understandable consistency
|
|
model. Leader election/log replication can come later if the cluster needs
|
|
automatic failover.
|
|
- SWIM: peer membership and failure suspicion should be gossip-shaped when the
|
|
mesh grows beyond small static clusters.
|
|
- CRDTs: offline multi-writer documents are valid only for types with declared,
|
|
deterministic merge semantics.
|
|
- Hashgraph: gossip-about-gossip is interesting for compact event provenance
|
|
and fair ordering research. CultNet should not adopt public-ledger machinery
|
|
or crypto-token assumptions as a shortcut to database correctness.
|
|
|
|
## Current Cut
|
|
|
|
Implemented:
|
|
|
|
- `CultNetDatabase`: database-style facade over a `CultCache`
|
|
- primary-shard write checks
|
|
- typed R3 watch streams
|
|
- raw CultNet put/delete application through shard policy
|
|
- `CultNetDatabaseServer`: schema-v0 bridge for snapshot, put, and delete
|
|
- `Client` and `Server` schema-v0 dispatch hooks
|
|
- `CultNetHost.Database` and `CultNetHost.DatabaseServer`
|
|
- subscription request/cancel/change messages
|
|
- server-side live change fanout for schema/key-filtered subscriptions
|
|
- shard catalog request/response messages
|
|
- shard descriptors with owner runtime id, epoch, schema filters, key prefix,
|
|
primary endpoints, replica endpoints, read replica endpoints, and region
|
|
- epoch-aware raw put/delete messages
|
|
- routing hints on shard authority errors
|
|
- injectable non-primary write forwarding policy
|
|
- schema-v0 write forwarder that dials advertised `rudp://host:port` primary
|
|
endpoints, while keeping `cultnet://host:port` as the LiteNetLib compatibility
|
|
lane
|
|
- 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
|
|
- wire-level shard log request/response messages
|
|
- raw put/delete representation for accepted mutation-log entries
|
|
- replica-side shard log application with epoch checks, gap rejection, and
|
|
idempotent replay
|
|
- shard log replicator that pulls non-primary shards from catalog-advertised
|
|
primary endpoints
|
|
- schema-v0 shard log fetcher for `rudp://host:port` endpoints, while keeping
|
|
`cultnet://host:port` as the LiteNetLib compatibility lane
|
|
- restart-safe replica cursor store, including a local MessagePack file-backed
|
|
implementation
|
|
- durable authoritative shard-log storage behind
|
|
`ICultNetShardMutationLogStore`, including a per-shard MessagePack file
|
|
implementation
|
|
- shard-log compaction watermarks; requests for compacted history return
|
|
`ResyncRequired` with `reason = "compacted_history"`
|
|
- shard-bounded snapshots with shard id, epoch, and represented log sequence
|
|
- replica snapshot recovery after compacted history, including local shard
|
|
replacement and cursor advancement
|
|
- CultMesh node option for file-backed authoritative shard logs with a default
|
|
`.cultmesh/shard-logs` path beside the cache file
|
|
- schema-v0 Verse catalog request/response messages
|
|
- CultMesh Verse discovery server bridge over the local Verse catalog
|
|
- schema-v0 peer exchange request/response messages
|
|
- CultMesh peer cards, peer catalog, peer exchange server bridge, and peer
|
|
exchange client
|
|
- CultMesh authority leases and lease catalog checks for peer role/shard
|
|
authorization
|
|
- client-authority scopes for locally predicted input documents
|
|
- predicted and reconciled database change kinds for client-side prediction
|
|
- simulation witness observations for mesh-side opinions about frame facts
|
|
- deterministic consensus candidates derived from witness observations
|
|
- committed CultMesh simulation fact documents written from quorum candidates
|
|
through shard authority
|
|
- gameplay-facing CultMesh session facade for prediction, observations,
|
|
catalogs, peer exchange, and quorum fact commits
|
|
- schema-v0 simulation observation and consensus candidate messages
|
|
- reactive simulation observation hub that receives witness gossip and emits
|
|
consensus candidates
|
|
- server-side observation bridge that accepts observation messages and replies
|
|
with current candidate messages
|
|
|
|
Not implemented yet:
|
|
|
|
- SWIM-style membership/failure detection beyond Python's hello-level health
|
|
monitor
|
|
- leader election or automatic shard failover
|
|
- cross-runtime scheduled peer-to-peer snapshot fanout beyond Python's local
|
|
fanout loop
|
|
- cross-runtime scheduled peer-to-peer fanout for observation gossip and
|
|
candidate propagation beyond Python's local fanout loop
|
|
- cross-runtime rollback/resimulation helpers for simulation frames
|
|
- declared CRDT merge policies
|
|
|
|
## Error Codes
|
|
|
|
`cultnet.error.v0` carries an optional machine-readable `code`. The set of codes is a closed enum
|
|
(`cultnet.error.schema.json`): Rust and TypeScript refuse a message whose code they do not know, they do
|
|
not guess. Adding a code is therefore a coordinated change across every runtime, made together: the schema (both
|
|
copies), the C# `CultNetErrorMessage` factory, the Rust `CultNetErrorCode`, and a captured-bytes case in
|
|
`packages/cultnet-rs/tests/error_message.rs` for it. TypeScript reads the schema. The current codes are
|
|
`selection_invalid`, `cursor_stale`, `cursor_invalid`, `reference_outside_target`, `variant_unsupported` and
|
|
`unowned_schema` (a write, or a remote put or delete, for a schema and key no shard owns).
|
|
|
|
## Live Invariants
|
|
|
|
- CultCache owns document schema, identity, local indexes, and local diffing.
|
|
- CultNet owns transport, shard authority, remote mutation delivery, and
|
|
subscription fanout.
|
|
- One primary owns writes for a shard until a stronger policy exists.
|
|
- Non-owner writes are rejected or explicitly forwarded. They are not silently
|
|
applied.
|
|
- Raw snapshot/put/delete messages must pass through `CultNetDatabase`, not
|
|
bypass shard policy.
|
|
- The accepted shard log is stored in replica catch-up wire form. Object logs
|
|
are for local inspection; wire logs are the durable replication authority.
|
|
- Log retention is part of the replication contract. A replica asking before
|
|
the compaction watermark must resync from a snapshot before applying newer
|
|
log entries.
|
|
- A shard snapshot represents a log sequence. Applying it replaces the local
|
|
shard view and advances the replica cursor to that sequence.
|
|
- Realtime subscriptions publish domain changes, not storage envelopes.
|
|
- Client-owned input documents may be predicted locally only inside explicit
|
|
`CultNetClientAuthorityScope` declarations.
|
|
- Predicted input state is not authoritative shared state. The shard log still
|
|
decides the committed ordering and emits reconciliation when it corrects or
|
|
confirms local prediction.
|
|
- Witness observations are immutable reports about a shard epoch, frame,
|
|
subject, claim kind, and claim hash.
|
|
- Consensus candidates are derived from observations. They may inform the
|
|
authoritative commit path, but they are not themselves committed world state
|
|
until a shard owner writes them into the log.
|
|
- `CultMeshSimulationFact` is the committed form of a quorum candidate. It is
|
|
written through `CultNetDatabase`, so normal shard authority and replication
|
|
rules still apply.
|
|
- `CultMeshGameSession` composes common gameplay surfaces. It does not create a
|
|
second authority path; all committed state still flows through shard-owned
|
|
database writes.
|
|
- Peer cards are contact candidates. They can advertise roles and authority
|
|
lease ids, but they do not grant authority without Verse-specific validation.
|
|
- Authority leases bind peer id, Verse id, roles, optional shard scope, issuer,
|
|
validity window, and signature. They are the first trust boundary above peer
|
|
exchange.
|
|
|
|
## Next Coherent Slice
|
|
|
|
Add discovery fanout and operational polish:
|
|
|
|
- standardize periodic gossip fanout over known peer cards beyond Python's
|
|
local schedulers and health monitor
|
|
- standardize cross-runtime authority lease signature algorithms beyond the
|
|
package-local Python HMAC verifier
|
|
- standardize cross-runtime simulation-frame rollback/resimulation helpers beyond
|
|
Python's local pending-prediction rollback surface
|
|
- standardize cross-runtime scheduled peer-to-peer snapshot fanout beyond
|
|
Python's local fanout loop
|
|
- standardize cross-runtime scheduled peer-to-peer fanout for observation gossip
|
|
and candidate propagation beyond Python's local fanout loop
|
|
|
|
The log is wire-readable, replicas can apply it explicitly, a pull loop can
|
|
drive non-primary shards from primary endpoints, and replica cursors can survive
|
|
restart. Client-owned input documents can now be predicted locally and
|
|
reconciled when the authoritative log arrives. Nodes can also aggregate witness
|
|
observations into deterministic consensus candidates for simulation facts like
|
|
"who shot first," and those reports now have schema-v0 wire contracts. The next
|
|
useful layer is making Verse discovery and operator defaults first-class.
|
|
Membership and leader election should wait until basic replica catch-up is
|
|
boring. Authoritative mutation logs can now survive process restart through
|
|
`ICultNetShardMutationLogStore`; compaction now has an honest resync boundary,
|
|
and the shard replicator can recover from that boundary through snapshot
|
|
replacement.
|