Outbox
This page documents the durable outbox layer in the sync module.
The outbox implements the core offline-first outbox pattern:
- All operations are persisted before any network attempt
- Operations are claimed before being processed
- Completion, retry and permanent failure are recorded durably
- Network failures never lose accepted local writes
Headers
#include <vix/sync/outbox/Outbox.hpp>
#include <vix/sync/outbox/OutboxStore.hpp>
#include <vix/sync/outbox/FileOutboxStore.hpp>Namespace:
vix::sync::outboxArchitecture Overview
The outbox layer is composed of:
- Outbox: high-level coordination and retry logic
- OutboxStore: abstract persistence contract
- FileOutboxStore: file-backed JSON implementation
- RetryPolicy: retry scheduling logic
- Operation: immutable sync payload unit
The Outbox itself contains no network logic. It delegates transport to the engine layer.
Outbox
Outbox is the high-level facade that enforces the outbox pattern.
Responsibilities:
- Persist operations before sending
- Generate ids and idempotency keys when required
- Apply retry policy
- Coordinate claim / complete / fail transitions
- Delegate persistence to
OutboxStore
Config
struct Config
{
std::string owner{"vix-sync"};
RetryPolicy retry{};
bool auto_generate_ids{true};
bool auto_generate_idempotency_key{true};
};Meaning:
- owner: logical worker/engine name used when claiming operations
- retry: retry scheduling policy
- auto_generate_ids: auto-create operation ids if missing
- auto_generate_idempotency_key: auto-create idempotency keys if missing
Core API
Outbox(Config cfg, std::shared_ptr<OutboxStore> store);
std::string enqueue(Operation op, std::int64_t now_ms);
std::vector<Operation> peek_ready(std::int64_t now_ms, std::size_t limit = 50);
bool claim(const std::string& id, std::int64_t now_ms);
bool complete(const std::string& id, std::int64_t now_ms);
bool fail(
const std::string& id,
const std::string& error,
std::int64_t now_ms,
bool retryable = true);Lifecycle
enqueue()persists operation immediatelypeek_ready()returns eligible candidatesclaim()marks operation as in-flightcomplete()marks successfail()schedules retry or permanent failure
OutboxStore
OutboxStore defines the minimal persistence contract required by Outbox.
It guarantees:
- Durable persistence
- Claim ownership tracking
- Retry scheduling
- Cleanup helpers
- Crash recovery support
It is transport-agnostic.
ListOptions
struct ListOptions
{
std::size_t limit{50};
std::int64_t now_ms{0};
bool only_ready{true};
bool include_inflight{false};
};Used to filter operations based on readiness and state.
Required Methods
virtual void put(const Operation& op) = 0;
virtual std::optional<Operation> get(const std::string& id) = 0;
virtual std::vector<Operation> list(const ListOptions& opt) = 0;
virtual bool claim(const std::string& id,
const std::string& owner,
std::int64_t now_ms) = 0;
virtual bool mark_done(const std::string& id,
std::int64_t now_ms) = 0;
virtual bool mark_failed(const std::string& id,
const std::string& error,
std::int64_t now_ms,
std::int64_t next_retry_at_ms) = 0;
virtual bool mark_permanent_failed(const std::string& id,
const std::string& error,
std::int64_t now_ms) = 0;
virtual std::size_t prune_done(std::int64_t older_than_ms) = 0;
virtual std::size_t requeue_inflight_older_than(std::int64_t now_ms,
std::int64_t timeout_ms) = 0;Implementations must preserve correctness guarantees.
Thread-safety is implementation-defined.
FileOutboxStore
FileOutboxStore is a simple, durable JSON-based implementation.
Designed for:
- Offline-first environments
- Crash recovery
- Local-first apps
- Zero external dependencies
It favors correctness and simplicity over throughput.
Config
struct Config
{
std::filesystem::path file_path{"./.vix/outbox.json"};
bool pretty_json{false};
bool fsync_on_write{false};
};Meaning:
- file_path: persistence location
- pretty_json: human-readable output
- fsync_on_write: stronger durability at cost of performance
Behavior
- Lazy load on first access
- In-memory representation protected by mutex
- Writes flushed to disk on mutation
- Optional fsync for crash durability
Crash Recovery
Outbox supports:
- Reclaiming timed-out in-flight operations
- Retry scheduling
- Permanent failure marking
- Pruning old completed operations
Recovery helpers:
requeue_inflight_older_than(now_ms, timeout_ms);
prune_done(older_than_ms);Offline-First Guarantees
The outbox pattern ensures:
- Local write is durable before network attempt
- Network failure never discards accepted state
- Retries are deterministic
- Idempotency prevents duplicate effects
- Recovery after crash is possible
This is foundational for Softadastra-style convergence.
Design Philosophy
Outbox is:
- Durable first
- Transport-agnostic
- Deterministic
- Explicit
- Policy-driven (RetryPolicy)
It enforces one invariant:
No network attempt without prior durable persistence.