The logo is in the public domain (see Credits). We are not affiliated with AdaCore.
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.
AI assistance was used for this project.
alr with crdtView on Alire Community Index.
| 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 |
alr index --add git+https://github.com/bladeacer/Ada_CRDT.git --name crdt
alr with crdtThen, include with "crdt"; in your .gpr file.
Clone and build locally:
git clone https://github.com/bladeacer/Ada_CRDT.git
cd ada_crdt
make build
make testThe 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 |
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 |
See the changelogs
and the migration guide
before you bump your alire.toml dependency. Wire format is auto-detected
(V1/V2/V3).
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 = 15Package: CRDT.Pn_Counters
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)
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 | 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);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 opsCRDT.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;
| 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.
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 timeAll 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.
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.
| 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.
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 demoControls: Q Quit, R Reset, P Pause, M Toggle Engine, C Cycle Clock_Kind.
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.
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:
- PN-Counter: Apache Cassandra distributed counters, Riak CRDTs
- LWW-Element-Set: Redis Enterprise, SoundCloud Roshi; Lamport (1978)
- RGA: Yjs / YATA (Kevin Jahns): block CRDT text editing
- Automerge (Martin Kleppmann et al.): JSON CRDT
- Fugue: tree-based interleaving prevention
Badges:
- adacovex: Ada/SPARK code/proof coverage, SPARK level, DO-178C HAL status tool
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.
MIT License.

