Files
simplexmq/spec/modules/Simplex/Messaging/Agent/Store.md
Evgeny @ SimpleX Chat 546ee1a0e1 update specs
2026-03-13 12:43:02 +00:00

5.6 KiB

Simplex.Messaging.Agent.Store

Domain entity types for agent persistence — queues, connections, messages, commands, and store errors.

Source: Agent/Store.hs

Overview

This module defines the data types that represent agent state. It contains no database operations — those are in AgentStore.hs. The key abstractions are:

  • Queue types (StoredRcvQueue, StoredSndQueue) parameterized by DBStored phantom type for new vs persisted distinction
  • Connection GADT (Connection') encoding the connection state machine at the type level
  • Message containers (RcvMsgData, SndMsgData, PendingMsgData) for the message lifecycle
  • Store errors (StoreError) including two sentinel errors with special semantics

Connection' — type-level state machine

The Connection' GADT encodes connection lifecycle as a type parameter: CNewCRcv/CSndCDuplex, plus CContact for reusable contact connections. SomeConn wraps an existential to store connections of unknown type.

TestEquality SConnType deliberately omits SCNewtestEquality SCNew SCNew returns Nothing. This is intentional: NewConnection has no queues and is not a valid target for type-level connection matching in store operations.

canAbortRcvSwitch — race condition boundary

See comments on canAbortRcvSwitch. The RSSendingQUSE and RSReceivedMessage states cannot be aborted because the sender may have already deleted the original queue. Aborting (deleting the new queue) at that point would break the connection with no recovery path.

ratchetSyncAllowed / ratchetSyncSendProhibited — cross-repo contract

See comments on ratchetSyncAllowed. Both functions carry the comment "this function should be mirrored in the clients" — simplex-chat must implement identical logic. The agent enforces these state checks, but the chat client also needs them for UI decisions (e.g., disabling send when ratchetSyncSendProhibited).

SEWorkItemError — worker suspension sentinel

SEWorkItemError is a sentinel error that triggers worker suspension when encountered during work item retrieval. The AnyStoreError typeclass exposes isWorkItemError for the worker framework (Agent/Client.hs) to detect this case. The comment "do not use!" means it should not be thrown for normal error conditions — only when the work item itself is corrupt/unreadable and the worker should stop rather than retry.

SEAgentError — store-level error wrapping

SEAgentError wraps AgentErrorType inside store operations. This allows store functions to return agent-level errors (e.g., connection state violations detected during a DB transaction) without breaking the ExceptT StoreError type. The "to avoid race conditions" rationale: checking a condition and acting on it must happen in the same DB transaction, so the agent error is returned through the store error channel.

See comment on InvShortLink. Stored separately from the connection because 1-time invitation short links have a "secure-on-read" property: accessing the link data on the router marks it as read, preventing undetected observation. The sndPrivateKey is persisted to allow retries of the link creation without generating new keys.

RcvQueueSub — subscription-optimized projection

RcvQueueSub strips cryptographic fields from RcvQueue, keeping only what's needed for subscription tracking in TSessionSubs. This reduces memory pressure when tracking thousands of subscriptions in STM.

rcvSMPQueueAddress exposes sender-facing ID

rcvSMPQueueAddress constructs the SMPQueueAddress from a receive queue using sndId (not rcvId). The address shared with senders in connection requests contains the sender ID, the public key derived from e2ePrivKey, and queueMode. The rcvId is never exposed externally.

enableNtfs is duplicated between queue and connection

enableNtfs exists on both StoredRcvQueue and ConnData. The comment marks it as "duplicated from ConnData." The queue-level copy enables subscription operations (which work at the queue level) to check notification status without loading the full connection.

deleteErrors — queue deletion retry counter

StoredRcvQueue has a deleteErrors :: Int field that counts failed deletion attempts. This allows the agent to give up on queue deletion after repeated failures rather than retrying indefinitely.

Two-level message preparation

SndMsgData optionally carries SndMsgPrepData with a sndMsgBodyId reference to a separately stored message body. PendingMsgData optionally carries PendingMsgPrepData with the actual AMessage body. This split allows large message bodies to be stored once and referenced by ID during the send pipeline, avoiding redundant serialization.

Per-message retry backoff

PendingMsgData includes msgRetryState :: Maybe RI2State — each pending message independently tracks its retry backoff state. This means messages that fail to send don't reset the retry timers of other pending messages in the same connection.

Soft deletion and optional contact connection

ConnData has deleted :: Bool for soft deletion — connections are marked deleted before queue cleanup completes. Invitation has contactConnId_ :: Maybe ConnId (note the trailing underscore) — invitations can outlive their originating contact connection.

SEBadQueueStatus is vestigial

SEBadQueueStatus is documented in the source as "Currently not used." It was intended for queue status transition validation but was never implemented.