Skip to content
bladeacerPublic

About

CRDT (Conflict-Free Replicated Data Types) library for Ada/SPARK.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Latest commit

 

History

148 Commits

Folders and files

Repository files navigation

crdt Alire crate badge SPARK DO-178C Tests docs

Ada CRDTs logo

The logo is in the public domain (see Credits). We are not affiliated with AdaCore.

CRDT

CRDT (Conflict-Free Replicated Data Types) library for Ada/SPARK.

The canonical repository is on GitHub. The Codeberg repository was archived because of Codeberg terms-of-service changes on AI-assisted code. We do not accept issues or pull requests there. Submit your proposed changes on GitHub instead.

The documentation is deployed at ada-crdt.readthedocs.io. The site carries no tracking script and no cookie, and it shows no paid advertisement and no flyout menu. Read the site transparency page for the full statement, including the search box and the hosting provider's aggregate page-view counting.

LLM Usage disclosure

AI assistance was used for this project.

Install

Alire Community Index

alr with crdt

View on Alire Community Index.

Quick Reference

Component Package API Docs
PN-Counter CRDT.Pn_Counters docs
LWW Set (Lamport, deprecated) CRDT.Lww_Element_Sets docs
LWW Set (any clock) CRDT.Lww_Sets docs
RGA Sequence CRDT.Rga docs
Clock strategies CRDT.Clocks docs
State-based sync CRDT.Sync.State_Based docs
Op-based sync CRDT.Sync.Op_Based docs
Sequence engines CRDT.Sequences docs
Post-quantum security CRDT.Security docs
Thread-safe wrappers CRDT.Protected docs
Bounded wrappers CRDT.Bounded docs
HLC CRDT.HLC docs

Local Index

alr index --add git+https://github.com/bladeacer/Ada_CRDT.git --name crdt
alr with crdt

Then, include with "crdt"; in your .gpr file.

Development

Clone and build locally:

git clone https://github.com/bladeacer/Ada_CRDT.git
cd ada_crdt
make build
make test

Documentation

The full index lives at ada-crdt.readthedocs.io. Highlights:

Reference Description
Usage guide Self-contained user guide: engines, clocks, sync, serialisation, security
Getting started Install path, first PN-Counter, LWW set, and sync loop
Sequence engines Yjs, Naive, and Fugue engines, and their garbage collection
Engine comparison Engine robustness against upstream Yjs and Automerge
Clock strategies Lamport, Vector, and Matrix clocks, and how to pick a default
Sync layers State-based versus operation-based sync, and the op-log lifecycle
Serialisation The V1, V2, and V3 wire formats, and the migration helpers
Security SHA-256, HMAC, and LMS signature verification
Containers and wrappers Bounded wrappers, protected types, multi-RGA, and the HLC
API reference Auto-generated package docs, public and private entities
SPARK coverage Every SPARK_Mode => Off location with its justification
Quality gates Every make target and what each gate proves
AI and LLM usage Disclosure, the evidence, and the bar a change must meet
DO-178C compliance PSAC, HLR, LLR, traceability, and verification results
Proof records The verified-condition ledger, and why a unit is skipped
Site transparency Search, traffic analytics, advertising, and the flyout menu
Changelog Release history
CI/CD The workflows and their local equivalents

Badges

The badges above report the current release state, and each badge links to the page that carries the full data:

Badge Source What it reports
Alire the Alire index The published crate version on the Alire community index
SPARK the badges page The proof level and the verification-condition counts
DO-178C the badges page The DAL-C assessment result
Tests VERIFICATION.md The passing test count per category
docs the badges page The docstring coverage of the public subprograms

Upgrading

See the changelogs and the migration guide before you bump your alire.toml dependency. Wire format is auto-detected (V1/V2/V3).


Core Types

PN-Counter (Actor Map)

Per-replica increments/decrements. Fixed memory (3 replicas = 3 slots), regardless of op count. See API docs for full interface reference.

with CRDT.Pn_Counters;

A : CRDT.Pn_Counters.PN_Counter (Max_Actors => 5);
B : CRDT.Pn_Counters.PN_Counter (Max_Actors => 5);

CRDT.Pn_Counters.Increment (A, 5, Actor => 1);
CRDT.Pn_Counters.Increment (B, 10, Actor => 2);

CRDT.Pn_Counters.Merge (A, B);  -- value = 15

Package: CRDT.Pn_Counters

LWW-Clocked-Set (any clock strategy)

Last-Writer-Wins set parameterised over any clock strategy (Lamport, Vector, or Matrix). See API docs for full interface reference.

with CRDT.Lww_Sets;
with CRDT.Clocks.Vector;
package V is new CRDT.Clocks.Vector (Max_Replicas => 8);
package S is new CRDT.Lww_Sets (Integer, 100, V.Clock_Time,
  Clk_Kind     => CRDT.Clocks.Clock_Vector,
  ">"          => V.">",
  Max          => V.Max,
  Write_Clock  => V.Write_Clock,
  Read_Clock   => V.Read_Clock);

Set : S.LWW_Clocked_Set (Capacity => 100);
TS  : V.Clock_Time := (others => 0);

S.Add (Set, 42, TS);
S.Add (Set, 99, TS);
S.Remove (Set, 42, TS);

Package: CRDT.Lww_Sets (generic over any CRDT.Clocks.* strategy)

RGA Sequence

Three backend engines, same API. See API docs for full details.

with CRDT.Rga;
package Seq is new CRDT.Rga (Character, 100);
use Seq;

A : RGA (Capacity => 100);
B : RGA (Capacity => 100);

Insert (A, 1, (Replica => 1, Seq => 1), 'a');
Insert (A, 2, (Replica => 1, Seq => 2), 'b');
Insert (B, 1, (Replica => 2, Seq => 1), 'x');

Merge (A, B);  -- convergent state

-- Iterate
Pos : Cursor := First (A);
while Has_Element (Pos) loop
   Put (Element (A, Pos));
   Next (A, Pos);
end loop;

Package: CRDT.Rga (default engine) or CRDT.Sequences.<Engine>

Engine Comparison

Engine Package Design Trade-off
Yjs (default) CRDT.Rga / CRDT.Sequences.Yjs Chunk-based blocks, structural splitting Fast bulk ops, larger tombstone overhead
Naive CRDT.Sequences.Naive Flat linked-list per element Simple, O(n) lookups
Fugue CRDT.Sequences.Fugue BST tree with Depth ordering Anti-interleaving, no GC rebalancing yet

Understand how suitable each backend is for a given use case.

-- Switch engine by changing the with line
with CRDT.Sequences.Naive;
package S is new CRDT.Sequences.Naive (Character, 100);

Sync Layer

See state-based sync and op-based sync for the full interface reference.

State-based (CvRDT) with delta sync and HLC:

with CRDT.Sync.State_Based;

Config : Sync_Config := (Max_Replicas => 4, Delta_Sync => True, HLC_Node => 1);
Local  : Replica_State := Create (Config);
Remote : Replica_State := Create (Config);

Merge (Local, Remote);

Operation-based (CmRDT) with bounded op log and ack/GC:

with CRDT.Sync.Op_Based;

Log : Op_Log (Capacity => 1000);

Append (Log, (Kind => Op_Insert, Seq => 1, Node => 1, Position => 1));
Append (Log, (Kind => Op_Delete, Seq => 2, Node => 1, Del_Position => 1));

Acknowledge (Log, Up_To_Seq => 1);  -- mark delivered
Compact (Log);                       -- purge acknowledged ops

Wrappers

  • CRDT.Protected: Thread-safe protected-object wrappers (no locking).
  • CRDT.Bounded: Compile-time bounded, zero-heap allocation.
with CRDT.Bounded;
package Bnd is new CRDT.Bounded.Bounded_RGA (Character, 100);
R : Bnd.Sequence;

Supporting Types

Package Role
CRDT.Core Replica_Id, Lamport_Time, Protocol_Version, VTime types
CRDT.HLC Hybrid Logical Clock (physical + logical timestamp)
CRDT.Rgas Multi-RGA container
CRDT.Bounded Compile-time bounded wrappers for the containers
CRDT.Protected Thread-safe protected types for the containers

See containers, wrappers, and the hybrid logical clock for when to use each wrapper.

HLC Example

with CRDT.HLC;

Clock : CRDT.HLC.Instance := CRDT.HLC.Create (Node => 1);
CRDT.HLC.Tick (Clock);   -- before sending
CRDT.HLC.Recv (Clock, Remote);  -- on receive, reconcile with remote time

Wire Protocol

All serialised CRDT state begins with a protocol version byte. The version is detected automatically on read.

V1: [4-byte Natural version][4-byte Total][4-byte Count]...
V2: [LEB128 version=2][LEB128 Total][LEB128 Count]...
V3: [LEB128 version=3][clock_kind byte][LEB128 Total][LEB128 Count]...
Version Format Auto-detected
V1 (legacy) Fixed-width Natural'Read/Write for all fields Yes (first 4 bytes)
V2 (default write) LEB128 for all integer fields Yes (first byte = 2)
V3 (clocked) LEB128 + clock kind discriminator byte Yes (first byte = 3)

Legacy types (LWW_Element_Sets, RGA, PN_Counters) continue to write V2 for maximum backward compatibility. New generic types (Lww_Sets) write V3 with the appropriate clock strategy identifier.

V1/V2 data can be migrated to V3 via CRDT.Serialization.Migrate_Header_To_V3.

LEB128 Encoding

All Natural fields in the wire format use LEB128 variable-length encoding (CRDT.Core.LEB128), producing 1-5 bytes per value instead of the fixed 4-byte Natural'Write. Small values (common for clocks, positions, and counts) use 1-2 bytes.

Fields encoded with LEB128:

  • Protocol version (1 byte)
  • Clock kind byte (V3 only)
  • Per-node element count in sets
  • Sequence length and replica/sequence ID pairs
  • Tombstone and strut counts in RGA chunks

This replaces the earlier fixed-width Natural'Write / Natural'Read format (Protocol_Version 1). Read_RGA rejects mismatched versions, enabling safe rolling upgrades.


Building

Command Action
make build Build library + tests
make check Full pre-commit gate: ascii, changelog, links, prose, SPARK coverage, build, tests, proof, coverage, compliance
make test Run test suite (see the test results)
make docs-check Prose gate: paragraph cap, sentence spacing, em dash, Latin abbreviation, line cap
make link-check Verify every markdown link and anchor resolves
make prove SPARK proofs via alr gnatprove
make demo Run Conway Game of Life Demo
make doc Generate Markdown API docs (see the API reference)
make clean Remove build artifacts

Prerequisites: Alire (manages GNAT automatically), Python 3 for make doc.


Demo

Conway's Game of Life Demo

The demo is a real-time TUI simulation. It stress-tests eventual consistency across three independent nodes. It uses LWW_Element_Set for cell state and Yjs RGA for text rows.

make demo

Controls: Q Quit, R Reset, P Pause, M Toggle Engine, C Cycle Clock_Kind.


SPARK Proof

Core packages (CRDT.Core, CRDT.Pn_Counters, CRDT.Clocks.*) are SPARK-proven at the Gold level (Stone + Bronze + Silver + Gold). Current proof statistics are auto-generated by make compliance. See docs/compliance/VERIFICATION.md. Generics (Sequences, LWW, RGA) depend on their instantiations.

Platform dependencies (wall clock, RNG, stream I/O) are excluded from formal proof. Runtime assertions (-gnata) give defensive coverage for generic bodies.

SPARK_Mode coverage (every SPARK_Mode => Off location with justification): the SPARK coverage page.


Credits

Logo:

  • Ada Logo Editor: The Ada Horizon logo and Aileron Bold font are both released under Creative Common Public Domain (CC0).

Technology Stack:

  • SPARK / Ada 2012: (AdaCore) language and dialect of choice
  • gnatprove: (AdaCore) formal verification of source code
  • Alire: (AdaCore) Ada/SPARK package manager
  • gnatformat: (AdaCore) code formatter for Ada
  • gnatdoc: (AdaCore) API documentation generator, interfaces docstrings with our Python script
  • VT100: Minimal Ada VT100 API library

Inspired by:

Badges:

  • adacovex: Ada/SPARK code/proof coverage, SPARK level, DO-178C HAL status tool

Contributing

We welcome contributions. Please read our Contributing Guide and Code of Conduct before you open an issue or pull request. Use the issue templates in .github/ISSUE_TEMPLATE/ for bug reports, feature requests, and security reports.

The quality gates page lists every make target and what each gate proves, and the AI and LLM usage page states the disclosure and the bar a generated change must meet.

License

MIT License.

About

CRDT (Conflict-Free Replicated Data Types) library for Ada/SPARK.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Contributors

Languages