Potable
Potable is an Elixir implementation of core AT Protocol repository primitives. It provides deterministic encoding, content addressing, signed commits, Merkle Search Tree indexing, CAR export/import, and a pure-functional repository engine.
Potable is designed to be used as a substrate inside larger systems, not as a full server implementation.
What Potable Implements
-
DAG-CBOR codec with IPLD constraints (
Potable.DagCbor) -
CID parsing/creation/verification (
Potable.CID) -
Merkle Search Tree (
Potable.MST) -
Algorithm-aware signing (
Potable.Signing: Ed25519, ES256K, ES256) -
Atproto commit objects v3 (
Potable.Commit) -
CAR v1 export/import (
Potable.CAR) -
Pure-functional repo engine (
Potable.Repository) -
Blockstore behaviour and in-memory backend (
Potable.BlockStore,Potable.BlockStore.Memory) -
Firehose primitives (
Potable.EventLog*,Potable.Sync.*) -
Identity verifier resolver behaviour and static resolver (
Potable.Identity.Resolver,Potable.Identity.StaticResolver) -
Supporting AT identifiers and helpers (
Potable.DID,Potable.NSID,Potable.AtUri,Potable.TID, etc.)
Design Guarantees
- Deterministic DAG-CBOR encoding for valid inputs
- Deterministic MST root for equivalent key/value sets
- Commit signing and signature verification with algorithm-aware key metadata
-
DID/revision-aware verifier resolution via
verify_repo_with_resolver/4 - Content-addressed integrity checks (CID vs bytes)
-
Strict CID decode/validation for
b...,z..., andQm...inputs - CAR import with typed errors (no runtime raises for content failures)
- Pure-functional repository operations (no hidden process state)
Quickstart
alias Potable.{BlockStore, Repository, Signing}
{:ok, store} = BlockStore.Memory.start_link()
{pub, priv} = Signing.generate_keypair()
did = "did:plc:potable-demo-1234567890"
{:ok, head} = Repository.create_repo(did, priv, store)
post = %{
"$type" => "app.bsky.feed.post",
"text" => "hello from potable",
"createdAt" => "2026-02-13T00:00:00Z"
}
writes = [{:create, "app.bsky.feed.post", "post-1", post}]
{:ok, head} = Repository.apply_writes(head, did, writes, priv, store)
{:ok, loaded} = Repository.get_record(head, "app.bsky.feed.post", "post-1", store)
{:ok, listed} = Repository.list_records(head, "app.bsky.feed.post", [limit: 10], store)
:ok = Repository.verify_repo(head, pub, store)
{:ok, car_bytes} = Repository.export_car(head, store)
{:ok, store2} = BlockStore.Memory.start_link()
{:ok, imported_head} = Repository.import_car(car_bytes, store2)
{:ok, _loaded_again} = Repository.get_record(imported_head, "app.bsky.feed.post", "post-1", store2)Module Map
| Area | Modules |
|---|---|
| Repository engine | Potable.Repository, Potable.Commit, Potable.MST, Potable.CAR, Potable.Signing |
| Codecs and formats | Potable.DagCbor, Potable.CID, Potable.Varint |
| Storage | Potable.BlockStore, Potable.BlockStore.Memory, Potable.Blob, Potable.Blob.Storage, Potable.Blob.MemoryStorage |
| Sync and firehose primitives | Potable.EventLog, Potable.EventLog.Memory, Potable.Sync.CommitEnvelope, Potable.Sync.Cursors, Potable.Sync.CAR |
| Identifiers and parsing | Potable.DID, Potable.NSID, Potable.AtUri, Potable.Handle, Potable.TID, Potable.Record, Potable.Identity.Resolver, Potable.Identity.StaticResolver |
Documentation
See the full manual in potable/docs/:
docs/00_overview.mddocs/01_quickstart.mddocs/02_data_formats.mddocs/03_mst.mddocs/04_commits_and_signatures.mddocs/05_repository_engine.mddocs/06_storage_backends.mddocs/07_known_limitations.mddocs/08_conformance_and_interop.mddocs/09_sync_and_firehose_primitives.md
Development
cd potable
mix test
mix docsCurrent Limitations
Potable.MST.put/4andPotable.MST.delete/3rebuild from flattened pairs (deterministic, not optimized).-
Potable includes firehose primitives, but does not ship a wire transport server (for example websocket
subscribeRepos) or full XRPC service layer. - Full production federation interop still requires external key distribution/trust plumbing (DID docs, key rotation policy, resolver policy).
- Continue adding cross-implementation ES256K/ES256 vectors against upstream repos as implementations evolve.