syntax = "proto3"; package stemedb.sync.v1; // SyncService enables node-to-node replication for StemeDB. // // The service supports two sync patterns: // 1. Gossip: Push new assertions to peers immediately after ingestion // 2. Anti-Entropy: Periodic Merkle root exchange and diff-based sync service SyncService { // Gossip pushes a new assertion to a peer. // Called immediately after local ingestion to propagate data quickly. rpc Gossip(GossipRequest) returns (GossipResponse); // ExchangeRoots compares Merkle roots to detect divergence. // If roots differ, the caller should fetch missing assertions. rpc ExchangeRoots(RootExchangeRequest) returns (RootExchangeResponse); // FetchAssertions retrieves assertion data by hash. // Used after ExchangeRoots to pull missing assertions. rpc FetchAssertions(FetchRequest) returns (FetchResponse); // Ping checks if a peer is alive and returns basic metadata. rpc Ping(PingRequest) returns (PingResponse); // GetLeaves returns all Merkle tree leaf hashes. // Used for computing the diff during anti-entropy sync. rpc GetLeaves(GetLeavesRequest) returns (GetLeavesResponse); } // GossipRequest pushes a single assertion to a peer. message GossipRequest { // BLAKE3 hash of the assertion (32 bytes) bytes assertion_hash = 1; // Serialized assertion data (rkyv format) bytes assertion_data = 2; // HLC timestamp components for causal ordering uint64 hlc_time = 3; uint32 hlc_counter = 4; bytes hlc_node_id = 5; // 16 bytes } message GossipResponse { // True if the assertion was accepted (stored or already existed) bool accepted = 1; // Error message if rejected (e.g., validation failure) string error = 2; } // RootExchangeRequest initiates Merkle root comparison. message RootExchangeRequest { // Local Merkle root hash (32 bytes) bytes merkle_root = 1; // Number of assertions in local tree uint64 assertion_count = 2; } message RootExchangeResponse { // Remote Merkle root hash (32 bytes) bytes merkle_root = 1; // Number of assertions in remote tree uint64 assertion_count = 2; // True if roots match (trees are identical) bool roots_match = 3; } // FetchRequest asks for assertion data by hash. message FetchRequest { // List of assertion hashes to fetch (max 1000 per request) repeated bytes hashes = 1; } message FetchResponse { // Retrieved assertions (may be fewer than requested if not found) repeated AssertionData assertions = 1; } // AssertionData pairs a hash with its serialized data. message AssertionData { // BLAKE3 hash of the assertion (32 bytes) bytes hash = 1; // Serialized assertion data (rkyv format) bytes data = 2; } // PingRequest is a health check with node identity. message PingRequest { // Sender's node ID (16 bytes) bytes node_id = 1; } message PingResponse { // Responder's node ID (16 bytes) bytes node_id = 1; // Number of assertions on this node uint64 assertion_count = 2; } // GetLeavesRequest requests all Merkle tree leaf hashes. message GetLeavesRequest { // Maximum number of leaves to return (0 = no limit, but capped at 10000) uint64 max_leaves = 1; } // GetLeavesResponse returns Merkle tree leaf hashes. message GetLeavesResponse { // All leaf hashes (each 32 bytes) repeated bytes leaves = 1; // True if there are more leaves than max_leaves bool truncated = 2; }