Vix.cpp docs moved to vixcpp.com
Skip to content

Sync

sync is the operation propagation module of Softadastra Engine.

It tracks local operations that need to move to other nodes.

The core rule is:

txt
Sync decides what should be sent.
Transport sends it.

Sync is not the network layer.

Sync is the layer that keeps propagation state visible, retryable, and deterministic.

Why sync exists

Softadastra is local-first.

A local write can happen before any peer is available.

Example:

txt
put user:1 = Gaspard

That value can be stored locally first.

But later, the operation may need to reach another node.

Sync exists to track that work.

txt
local operation

store apply

sync operation

outbox

queue

batch

transport later

The sync module makes delayed propagation explicit.

What sync provides

The sync module provides:

txt
SyncConfig
SyncContext
SyncEngine
SyncOperation
SyncScheduler
AckTracker
ConflictResolver
outbox
queue
retry
ACK tracking
remote apply
conflict policy
tick

It allows the engine to:

  • submit local operations
  • assign sync ids
  • track operation versions
  • queue operations
  • produce batches
  • retry expired work
  • track acknowledgements
  • receive remote operations
  • resolve conflicts
  • report sync state

What sync does not do

sync must not:

  • open TCP sockets
  • send UDP packets
  • discover peers
  • own transport connections
  • own CLI parsing
  • format terminal output
  • hide failed operations
  • delete local state on network failure

The rule is:

txt
Store applies local state.
Sync tracks propagation.
Transport delivers messages.
Discovery finds peers.
Metadata describes nodes.

Include

Use the top-level include:

cpp
#include <softadastra/sync/Sync.hpp>

Module location

The module lives in:

txt
modules/sync/

Typical structure:

txt
modules/sync/
├── include/
│   └── softadastra/sync/
│       ├── ack/
│       ├── conflict/
│       ├── core/
│       ├── engine/
│       ├── outbox/
│       ├── scheduler/
│       ├── types/
│       └── Sync.hpp
├── src/
├── examples/
├── tests/
├── README.md
├── CMakeLists.txt
└── CHANGELOG.md

The exact structure can evolve, but the responsibility should stay stable:

track operation propagation and make sync state observable.

Main concepts

The sync module is built around these concepts:

txt
SyncConfig
SyncContext
SyncEngine
SyncOperation
Outbox
Queue
Batch
ACK
Retry
Conflict
Tick

The normal flow is:

txt
store operation

submit local operation

sync operation created

outbox entry created

operation queued

next batch

transport can send

SyncConfig

SyncConfig configures sync behavior.

It can define:

txt
node id
durability mode
auto queue behavior
ACK requirement
ACK timeout
retry interval
maximum retries
conflict policy
batch size

Example:

cpp
auto config =
    sync::core::SyncConfig::durable("node-a");

The node id matters because sync operations need an origin.

Node id

Every sync operation should know where it came from.

Example:

txt
node-a

A local operation submitted by node-a can later be identified as coming from that node.

The node id is used for:

  • operation origin
  • version ownership
  • conflict resolution
  • diagnostics
  • ACK tracking
  • transport messages

Good node ids:

txt
node-a
node-local
drive-client
metadata-node
desktop-1

Avoid empty node ids.

SyncContext

SyncContext wires the sync engine to the local store and configuration.

Example:

cpp
sync::core::SyncContext context{store, config};

A valid context needs:

  • store engine
  • sync config
  • valid node id
  • valid sync settings

Example validation:

cpp
if (!context.is_valid())
{
    std::cerr << "invalid sync context\n";
    return 1;
}

SyncEngine

SyncEngine is the main runtime object of the sync module.

It owns sync state.

It can:

  • submit local operations
  • receive remote operations
  • queue operations
  • produce next batch
  • retry expired operations
  • track ACKs
  • report state

Example:

cpp
sync::engine::SyncEngine engine{context};

SyncOperation

SyncOperation is the propagation unit.

It wraps a store operation with sync metadata.

A sync operation can contain:

txt
sync id
origin node id
version
store operation
timestamp
status
retry count

The exact fields depend on the current implementation.

The important point is:

txt
store operation says what changed
sync operation says how that change propagates

Local operation submit flow

A local store operation becomes sync work through submit_local_operation.

Flow:

txt
store Operation

SyncEngine::submit_local_operation

SyncOperation created

sync id assigned

version assigned

outbox entry inserted

operation queued, if auto_queue is enabled

Basic sync example

cpp
#include <filesystem>
#include <iostream>

#include <softadastra/store/Store.hpp>
#include <softadastra/sync/Sync.hpp>

using namespace softadastra;

int main()
{
    std::cout << "== BASIC SYNC EXAMPLE ==\n";

    const std::string wal_path = "basic_sync_store.wal";
    std::filesystem::remove(wal_path);

    store::engine::StoreEngine store{
        store::core::StoreConfig::durable(wal_path)};

    auto config =
        sync::core::SyncConfig::durable("node-a");

    sync::core::SyncContext context{store, config};

    if (!context.is_valid())
    {
        std::cerr << "invalid sync context\n";
        return 1;
    }

    sync::engine::SyncEngine engine{context};

    auto operation = store::core::Operation::put(
        store::types::Key{"user:1"},
        store::types::Value::from_string("Gaspard"));

    auto submitted = engine.submit_local_operation(operation);

    if (submitted.is_err())
    {
        std::cerr << "submit failed: "
                  << submitted.error().message()
                  << "\n";

        return 1;
    }

    std::cout << "Submitted sync id: "
              << submitted.value().sync_id
              << "\n";

    auto batch = engine.next_batch();

    std::cout << "Batch size: "
              << batch.size()
              << "\n";

    for (const auto &envelope : batch)
    {
        std::cout << "Ready to send: "
                  << envelope.operation.sync_id
                  << " version="
                  << envelope.operation.version
                  << "\n";
    }

    std::filesystem::remove(wal_path);

    return 0;
}

Outbox

The outbox stores operations that need propagation.

It can contain entries in states such as:

txt
queued
in-flight
acknowledged
failed

The outbox answers:

  • what work is pending?
  • what work is ready?
  • what work is waiting for ACK?
  • what work failed?

This is why sync state is observable.

Queue

Queued operations are ready to be selected for a batch.

Flow:

txt
operation submitted

outbox entry created

queued

next_batch selects it

If auto_queue is disabled, operations can be submitted without being queued immediately.

Manual queue example

cpp
#include <filesystem>
#include <iostream>

#include <softadastra/store/Store.hpp>
#include <softadastra/sync/Sync.hpp>

using namespace softadastra;

int main()
{
    std::cout << "== MANUAL QUEUE EXAMPLE ==\n";

    const std::string wal_path = "manual_queue_store.wal";
    std::filesystem::remove(wal_path);

    store::engine::StoreEngine store{
        store::core::StoreConfig::durable(wal_path)};

    auto config =
        sync::core::SyncConfig::durable("node-a");

    config.auto_queue = false;

    sync::core::SyncContext context{store, config};
    sync::engine::SyncEngine engine{context};

    auto operation = store::core::Operation::put(
        store::types::Key{"manual:key"},
        store::types::Value::from_string("manual-value"));

    auto submitted = engine.submit_local_operation(operation);

    if (submitted.is_err())
    {
        std::cerr << "submit failed\n";
        return 1;
    }

    std::cout << "Queued before manual queue: "
              << engine.state().queued_count
              << "\n";

    const auto &sync_id = submitted.value().sync_id;

    if (!engine.queue_operation(sync_id))
    {
        std::cerr << "failed to queue operation\n";
        return 1;
    }

    std::cout << "Queued after manual queue: "
              << engine.state().queued_count
              << "\n";

    auto batch = engine.next_batch();

    std::cout << "Batch size: "
              << batch.size()
              << "\n";

    std::filesystem::remove(wal_path);

    return 0;
}

Batch

A batch is a group of sync operations ready to send.

Flow:

txt
queued operations

next_batch

batch of envelopes

transport can send

A batch can exist even when transport is disabled.

That matters because sync and transport are separate.

txt
Sync produces batches.
Transport delivers batches.

SyncScheduler

SyncScheduler runs one sync tick.

A tick can:

  • retry expired operations
  • prune acknowledged operations
  • select queued operations
  • produce a batch
  • return tick information

The scheduler is useful when the application wants a deterministic manual sync step.

Scheduler tick example

cpp
#include <filesystem>
#include <iostream>

#include <softadastra/store/Store.hpp>
#include <softadastra/sync/Sync.hpp>

using namespace softadastra;

int main()
{
    std::cout << "== SYNC SCHEDULER TICK EXAMPLE ==\n";

    const std::string wal_path = "scheduler_tick_store.wal";
    std::filesystem::remove(wal_path);

    store::engine::StoreEngine store{
        store::core::StoreConfig::durable(wal_path)};

    auto config =
        sync::core::SyncConfig::durable("node-a");

    sync::core::SyncContext context{store, config};
    sync::engine::SyncEngine engine{context};
    sync::scheduler::SyncScheduler scheduler{engine};

    auto operation = store::core::Operation::put(
        store::types::Key{"task:1"},
        store::types::Value::from_string("sync-me"));

    auto submitted = engine.submit_local_operation(operation);

    if (submitted.is_err())
    {
        std::cerr << "submit failed\n";
        return 1;
    }

    auto tick = scheduler.tick(false);

    std::cout << "Tick batch size: "
              << tick.batch_size()
              << "\n";

    std::cout << "Tick has work: "
              << tick.has_work()
              << "\n";

    for (const auto &envelope : tick.batch)
    {
        std::cout << "Transport should send: "
                  << envelope.operation.sync_id
                  << "\n";
    }

    std::filesystem::remove(wal_path);

    return 0;
}

Tick flow

A sync tick follows this model:

txt
tick

retry expired operations

prune completed operations, if requested

select queued operations

produce batch

return tick result

A tick should expose:

  • retried count
  • pruned count
  • batch size
  • has work

Manual tick behavior makes sync easier to test.

AckTracker

AckTracker tracks operations waiting for acknowledgement.

ACKs are useful when a sender needs to know that a remote node received or applied an operation.

Flow:

txt
operation sent

track sync id

wait for ACK

ACK received

mark acknowledged

prune later

ACK tracker example

cpp
#include <iostream>

#include <softadastra/sync/Sync.hpp>

using namespace softadastra;

int main()
{
    std::cout << "== ACK TRACKER EXAMPLE ==\n";

    sync::ack::AckTracker tracker;

    tracker.track(
        "node-a-1",
        core::time::Duration::from_seconds(10));

    std::cout << "Tracked count: "
              << tracker.size()
              << "\n";

    std::cout << "Waiting: "
              << tracker.is_waiting("node-a-1")
              << "\n";

    tracker.ack("node-a-1");

    std::cout << "Acknowledged: "
              << tracker.acknowledged("node-a-1")
              << "\n";

    auto removed = tracker.prune_received();

    std::cout << "Removed: "
              << removed
              << "\n";

    return tracker.empty() ? 0 : 1;
}

ACK timeout

If an operation requires ACK and none arrives before timeout:

txt
operation in-flight

ACK timeout reached

operation becomes retry candidate

retry policy decides next step

This keeps failed delivery visible.

Retry

Retry moves expired or failed in-flight work back into the queue when allowed.

Flow:

txt
operation sent

ACK timeout or delivery failure

retry count checked

operation re-queued

next tick can send again

If retry limit is reached:

txt
max retries reached

operation marked failed

failed_count increases

Failed sync does not mean local data is lost.

It means propagation failed.

Retry example

cpp
#include <filesystem>
#include <iostream>

#include <softadastra/store/Store.hpp>
#include <softadastra/sync/Sync.hpp>

using namespace softadastra;

int main()
{
    std::cout << "== SYNC RETRY EXAMPLE ==\n";

    const std::string wal_path = "retry_store.wal";
    std::filesystem::remove(wal_path);

    store::engine::StoreEngine store{
        store::core::StoreConfig::durable(wal_path)};

    auto config = sync::core::SyncConfig::fast("node-a");
    config.require_ack = true;
    config.max_retries = 3;
    config.ack_timeout = core::time::Duration::from_millis(1);
    config.retry_interval = core::time::Duration::from_millis(1);

    sync::core::SyncContext context{store, config};
    sync::engine::SyncEngine engine{context};

    auto operation = store::core::Operation::put(
        store::types::Key{"retry:key"},
        store::types::Value::from_string("retry-value"));

    auto submitted = engine.submit_local_operation(operation);

    if (submitted.is_err())
    {
        std::cerr << "submit failed\n";
        return 1;
    }

    auto batch = engine.next_batch();

    std::cout << "Initial batch size: "
              << batch.size()
              << "\n";

    auto retried = engine.retry_expired();

    std::cout << "Retried count: "
              << retried
              << "\n";

    std::cout << "Outbox size: "
              << engine.state().outbox_size
              << "\n";

    std::filesystem::remove(wal_path);

    return 0;
}

ConflictResolver

ConflictResolver decides what happens when a remote operation conflicts with local state.

A conflict can happen when:

  • local node changed a key
  • remote node also changed the same key
  • operations arrive in different order
  • versions differ
  • timestamps differ

Conflict resolution must be deterministic.

Conflict policy

A conflict policy defines the decision rule.

Possible policies include:

txt
LastWriteWins
KeepLocal
ApplyRemote
Custom, later

The current implementation may support a subset.

The important rule is:

conflict decisions should be explicit and inspectable

Conflict example

cpp
#include <iostream>

#include <softadastra/store/Store.hpp>
#include <softadastra/sync/Sync.hpp>

using namespace softadastra;

int main()
{
    std::cout << "== CONFLICT POLICY EXAMPLE ==\n";

    auto local_entry = store::core::Entry::make(
        store::types::Key{"doc:1"},
        store::types::Value::from_string("local"),
        1);

    auto remote_operation = store::core::Operation::put(
        store::types::Key{"doc:1"},
        store::types::Value::from_string("remote"));

    auto resolution =
        sync::conflict::ConflictResolver::resolve(
            local_entry,
            remote_operation,
            sync::types::ConflictPolicy::LastWriteWins,
            "node-a",
            "node-b");

    std::cout << "Conflict detected: "
              << resolution.conflict_detected
              << "\n";

    std::cout << "Apply remote: "
              << resolution.apply_remote
              << "\n";

    std::cout << "Keep local: "
              << resolution.keep_local
              << "\n";

    return resolution.is_valid() ? 0 : 1;
}

Remote apply

Remote apply receives an operation from another node and applies it locally when allowed.

Flow:

txt
remote sync operation received

validate operation

detect conflict

apply conflict policy

apply store operation, if accepted

return apply result

Remote apply example

cpp
#include <filesystem>
#include <iostream>

#include <softadastra/store/Store.hpp>
#include <softadastra/sync/Sync.hpp>

using namespace softadastra;

int main()
{
    std::cout << "== REMOTE APPLY EXAMPLE ==\n";

    const std::string wal_path = "remote_apply_store.wal";
    std::filesystem::remove(wal_path);

    store::engine::StoreEngine store{
        store::core::StoreConfig::durable(wal_path)};

    auto config =
        sync::core::SyncConfig::durable("node-local");

    sync::core::SyncContext context{store, config};
    sync::engine::SyncEngine engine{context};

    auto remote_store_operation = store::core::Operation::put(
        store::types::Key{"remote:user:1"},
        store::types::Value::from_string("Remote value"));

    auto remote_sync_operation =
        sync::core::SyncOperation::remote(
            "node-remote-1",
            "node-remote",
            1,
            remote_store_operation);

    auto result =
        engine.receive_remote_operation(remote_sync_operation);

    if (result.is_err())
    {
        std::cerr << "remote apply failed: "
                  << result.error().message()
                  << "\n";

        return 1;
    }

    if (result.value().applied)
    {
        std::cout << "Remote operation applied\n";
    }

    auto entry = store.get(
        store::types::Key{"remote:user:1"});

    if (entry.has_value())
    {
        std::cout << "Stored value: "
                  << entry->value.to_string()
                  << "\n";
    }

    std::filesystem::remove(wal_path);

    return 0;
}

Sync state

The sync engine should expose state.

Useful fields include:

txt
outbox_size
queued_count
in_flight_count
acknowledged_count
failed_count
last_submitted_version
last_applied_remote_version
total_retries

The exact fields depend on the current implementation.

The important idea is:

sync must be observable

A developer should be able to inspect what is pending, queued, acknowledged, or failed.

Outbox size

outbox_size tells how much work is tracked by sync.

Example: outbox_size = 1

This means sync knows about one operation. It does not mean the operation has been delivered.

Queued count

queued_count tells how much work is ready for a batch.

Example: queued_count = 1

This means next_batch() can likely produce work.

In-flight count

in_flight_count tells how many operations are currently sent or waiting for ACK.

This matters when ACK tracking is enabled.

Acknowledged count

acknowledged_count tells how many operations have been acknowledged.

Acknowledged operations can be pruned later when safe.

Failed count

failed_count tells how many operations failed propagation.

A failed sync operation does not automatically delete local store data.

It means propagation failed.

Sync and store

Store and sync are connected but separate.

txt
Store -> current local state
Sync  -> propagation state

A store operation can become sync work:

txt
Operation::Put

SyncOperation

Outbox

The store should not decide retry policy.

The sync module should not own current key-value state.

Sync and WAL

WAL makes operations durable.

Sync makes operations propagatable.

txt
WAL  -> local operation history
Sync -> propagation tracking

A local operation can be:

  • durable locally
  • pending remotely

This distinction is important.

Sync and transport

Transport delivers sync batches.

Relationship:

txt
SyncEngine::next_batch()

TransportEngine::send_sync_batch()

Sync does not open sockets.

Transport does not decide conflict resolution.

Sync and discovery

Discovery finds peers.

Sync does not find peers.

Flow:

txt
Discovery finds peer

Transport connects peer

Sync batch sent to peer

No peers means sync work may remain pending.

It does not mean local data is invalid.

Sync and metadata

Metadata describes nodes.

Sync uses node identity.

Example:

txt
metadata node id = node-a
sync operation origin = node-a

Metadata can help make sync diagnostics easier to understand.

Sync and CLI

CLI can expose sync commands.

Correct direction:

txt
CLI command

SyncEngine

Wrong direction:

txt
SyncEngine

CLI output

Sync should return structured state.

CLI should format it.

Sync and SDK

The SDK wraps sync behind simpler methods.

C++ SDK:

txt
client.sync_state()
client.tick()

JavaScript SDK:

txt
client.syncStateInfo()
client.tick()

The SDK should hide lower-level wiring.

The engine keeps sync behavior explicit.

Local-first behavior

Sync must not block local writes.

Correct behavior:

txt
local write

store apply

sync track

network can fail later

Wrong behavior:

txt
local write

wait for remote peer

only then apply locally

That is not local-first.

Offline behavior

When offline:

txt
local write still works
sync operation enters outbox
transport is unavailable
operation remains pending
retry later

Offline is a normal state.

The sync module should expose it clearly.

Retry behavior

Retry should be controlled by configuration.

Important settings:

txt
require_ack
ack_timeout
retry_interval
max_retries

A good retry system should answer:

  • how many retries happened?
  • which operations failed?
  • which operations are still queued?
  • when will they be retried?

ACK behavior

ACK behavior should be optional.

Some flows may require ACKs. Some may only need best-effort delivery.

When ACKs are enabled, sync should track waiting operations.

When ACKs are disabled, sync may mark work differently after batch selection.

The policy should be explicit.

Conflict behavior

Conflict behavior should be deterministic.

Good conflict resolution:

txt
same local entry
same remote operation
same policy

same decision

Avoid hidden random conflict behavior.

Remote operation validation

Remote operations should be validated before apply.

Reject operations with:

  • empty sync id
  • empty origin node id
  • invalid version
  • invalid store operation
  • invalid key
  • invalid payload

Invalid remote operations should not corrupt local state.

Sync errors

Sync should return explicit errors.

Possible errors:

txt
invalid sync context
invalid node id
invalid local operation
invalid remote operation
operation not found
queue failed
retry failed
conflict resolution failed
store apply failed
ACK tracking failed

Do not hide sync failures.

Invalid context

A sync context is invalid if required dependencies are missing or configuration is bad.

Example:

cpp
if (!context.is_valid())
{
    std::cerr << "invalid sync context\n";
    return 1;
}

The error should identify the real problem when possible.

Invalid operation

An invalid operation should not enter the outbox.

Bad:

txt
invalid operation

queued anyway

Better:

txt
invalid operation

return error

outbox unchanged

Operation not found

Manual queue or ACK operations may reference unknown sync ids.

The result should be clear.

Example:

txt
sync operation not found: node-a-42

Retry exhausted

When max retries is reached:

txt
operation retry count >= max_retries

mark failed

failed count increases

Failed work should remain inspectable.

Conflict failure

If conflict resolution cannot decide safely, the operation should not be applied silently.

Return a clear error or resolution result.

Store apply failure

Remote apply can fail if the underlying store rejects the operation.

The sync engine should return the store error with context.

Example:

txt
remote apply failed: invalid key

Examples

Current useful examples include:

txt
ack_tracker.cpp
basic_sync.cpp
conflict_policy.cpp
manual_queue.cpp
remote_apply.cpp
retry.cpp
scheduler_tick.cpp

Recommended order:

  1. basic_sync.cpp
  2. manual_queue.cpp
  3. scheduler_tick.cpp
  4. ack_tracker.cpp
  5. retry.cpp
  6. conflict_policy.cpp
  7. remote_apply.cpp

This order moves from local operation tracking to remote application.

Run examples

From the engine repository:

sh
cd ~/softadastra/softadastra

Build:

sh
vix build

Or with CMake:

sh
cmake --preset dev-ninja
cmake --build --preset build-ninja

Find binaries:

sh
find build-ninja -type f -executable

Run the relevant sync example binary from the build output.

Testing sync

Sync tests should verify:

  • valid context
  • invalid context
  • submit local operation
  • auto queue enabled
  • auto queue disabled
  • manual queue
  • next batch
  • scheduler tick
  • ACK track
  • ACK receive
  • ACK prune
  • retry expired
  • retry exhausted
  • remote apply
  • conflict resolution
  • invalid remote operation
  • state counters

Good sync test flow

Basic submit test:

txt
create store
create sync config
create sync engine
submit local operation
expect outbox size = 1
expect queued count = 1

Manual queue test:

txt
auto_queue = false
submit operation
expect queued count = 0
queue operation manually
expect queued count = 1

Retry test:

txt
require ACK
submit operation
create batch
wait until timeout
retry expired
expect retried count > 0

Remote apply test:

txt
create remote operation
receive remote operation
expect store contains remote value

Conflict test:

txt
create local entry
create remote operation
resolve conflict
expect deterministic decision

Design rules

The sync module should follow these rules:

  1. Track propagation, not transport.
  2. Keep sync state observable.
  3. Keep local writes independent from network.
  4. Use explicit sync ids.
  5. Use deterministic versions.
  6. Return explicit errors.
  7. Keep retry policy configurable.
  8. Keep ACK behavior explicit.
  9. Keep conflict resolution deterministic.
  10. Do not hide failed operations.

Common mistakes

Making sync open network sockets

Wrong:

txt
SyncEngine opens TCP connection

Better:

txt
SyncEngine produces batch
TransportEngine sends batch

Treating sync failure as local data loss

Wrong:

txt
sync failed

delete local value

Better:

txt
sync failed

mark propagation failed

local store remains valid

Hiding failed operations

Wrong:

txt
retry exhausted

drop operation silently

Better:

txt
retry exhausted

mark failed

failed_count increases

Making discovery required

Wrong:

txt
no peer discovered

cannot write locally

Better:

txt
no peer discovered

write locally

sync later

Making conflict resolution random

Wrong:

txt
sometimes local wins, sometimes remote wins

Better:

txt
policy decides deterministically

Ignoring ACK timeout

If ACKs are required, missing ACKs must be visible.

Create store and sync engine:

cpp
store::engine::StoreEngine store{
    store::core::StoreConfig::durable("data/node-a.wal")};

auto config =
    sync::core::SyncConfig::durable("node-a");

sync::core::SyncContext context{store, config};

if (!context.is_valid())
{
    return 1;
}

sync::engine::SyncEngine engine{context};

Submit local operation:

cpp
auto operation = store::core::Operation::put(
    store::types::Key{"user:1"},
    store::types::Value::from_string("Gaspard"));

auto submitted = engine.submit_local_operation(operation);

if (submitted.is_err())
{
    return 1;
}

Create batch:

cpp
auto batch = engine.next_batch();

for (const auto &envelope : batch)
{
    // transport can send envelope.operation
}

Run tick:

cpp
sync::scheduler::SyncScheduler scheduler{engine};

auto tick = scheduler.tick(false);

if (tick.has_work())
{
    // send tick.batch through transport
}

API reference

Main areas:

AreaPurpose
coreSync config, context, operation
engineSyncEngine
schedulerManual sync tick
ackACK tracking
conflictConflict resolution
typesSync types and policies
outboxPropagation state

Main types

TypePurpose
SyncConfigConfigures sync behavior
SyncContextWires sync to store and config
SyncEngineMain sync runtime
SyncOperationPropagation operation
SyncSchedulerRuns manual ticks
AckTrackerTracks acknowledgements
ConflictResolverResolves local/remote conflicts
ConflictPolicyDefines conflict rule

Common methods

MethodPurpose
submit_local_operation(operation)Track a local operation
next_batch()Return queued operations ready to send
queue_operation(sync_id)Queue an operation manually
retry_expired()Retry expired in-flight work
receive_remote_operation(operation)Apply remote sync operation
state()Inspect sync state

Only document a method as stable when it exists in the current public API.

Summary

sync is the operation propagation module of Softadastra Engine.

It provides:

txt
SyncConfig
SyncContext
SyncEngine
SyncOperation
SyncScheduler
AckTracker
ConflictResolver
outbox
queue
retry
ACK tracking
remote apply

The key idea is:

Sync makes local operations visible, queued, retryable, and ready for transport.

It does not open sockets, discover peers, or own application state.

Next step

Continue with transport:

Go to Transport

Released under the Apache License 2.0.