Skip to content

08 Offline Event Log and Outbox Engine

Irshad Ali edited this page Sep 3, 2026 · 1 revision

08 — Offline Event Log & Outbox Engine

Corresponding Specifications: sys-arch/04-offline-event-log-architecture.md, sys-arch/08-resource-limits-backpressure-architecture.md
Key Crates: crates/siar-event-log, crates/siar-storage, crates/siar-messaging


1. Monotonic Append-Only Event Log Architecture

Every mutation in SIAR (message creation, read receipt, contact verification, profile update, group membership change) is recorded as an immutable event in an append-only log:

pub struct EventEnvelope {
    pub sequence_number: u64,          // Monotonically increasing per-device sequence
    pub event_id: EventId,             // BLAKE3 hash of payload + metadata
    pub account_id: AccountId,         // Originating author
    pub device_id: DeviceId,           // Originating physical device
    pub timestamp: Timestamp,          // Physical wall-clock time
    pub lamport_clock: u64,            // Logical causal ordering
    pub payload: EventPayload,         // Type-specific event data
    pub signature: Signature,          // Device Ed25519 signature
}
+-------------------------------------------------------------------------------+
|                       Local Append-Only Event Stream                          |
|  [Seq 1: KeyInit] -> [Seq 2: MsgSent] -> [Seq 3: ReadReceipt] -> [Seq 4: Join]|
+-------------------------------------------------------------------------------+
         |                                  |
         v (Indexed by Cursors)             v (Persisted to Stoolap SQL)
  [Sync Engine to Device B]         [Local App State Views]

2. Outbox Delivery State Machine

When a message or command is initiated while the node is completely offline, it is written to the transactional OutboxRepo in siar-storage:

stateDiagram-v2
    [*] --> Pending: User Enqueues Message
    Pending --> Sending: Radio Link Established
    Sending --> Sent: Link Acknowledges Ingest
    Sending --> Carried: Transferred to DTN Mule
    Carried --> Delivered: Recipient Submits Delivery ACK
    Sent --> Delivered: Direct Link Delivers to Recipient
    Delivered --> Read: Recipient Opens Conversation
    Sending --> Failed: Hop Limit / TTL Expired
    Failed --> Pending: User Triggers Manual Retry
Loading

State Definitions

  • Pending: Queued in local persistent storage, waiting for an appropriate radio link or transport carrier.
  • Sending: Actively transmitting across one or more physical links.
  • Carried: Transferred to a trusted DTN mule node; local sender retains copy until confirmed delivery.
  • Delivered: Destination node has received, verified, and stored the payload.
  • Read: Destination recipient has focused the conversation view (signed cryptographic read receipt received).

3. Backoff Retry Engine with Jitter

To prevent channel congestion and radio contention when multiple nodes reconnect simultaneously, siar-messaging implements exponential backoff with randomized full jitter:

$$T_{\text{wait}} = \min\left(T_{\text{max}}, T_{\text{base}} \cdot 2^{\text{attempt}}\right) \times \text{Uniform}(0.5, 1.5)$$

pub struct RetrySchedule {
    pub base_delay_ms: u64,    // e.g., 500 ms
    pub max_delay_ms: u64,     // e.g., 60,000 ms
    pub max_attempts: u32,     // e.g., 10 attempts before DTN fallback
    pub backoff_factor: f64,   // 2.0
    pub jitter_ratio: f64,     // 0.25 (±25% randomized window)
}

4. Stoolap Embedded SQL Schemas

All message entities, contact trust records, outbox tickets, and sync cursors are persisted locally using Stoolap—a zero-dependency, pure-Rust embedded SQL storage engine with zero external C dynamic library requirements:

CREATE TABLE IF NOT EXISTS messages (
    id TEXT PRIMARY KEY,
    conversation_id TEXT NOT NULL,
    sender_id TEXT NOT NULL,
    recipient_id TEXT,
    content_payload BLOB NOT NULL,
    media_blob_id TEXT,
    delivery_status TEXT NOT NULL,
    created_at INTEGER NOT NULL,
    sequence_no INTEGER NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_messages_conv ON messages(conversation_id, created_at);
CREATE INDEX IF NOT EXISTS idx_messages_status ON messages(delivery_status);

Clone this wiki locally