Architecture #
For a first walkthrough, read the repository guide. This document records the design boundaries; README owns operating instructions, and production-plan.md owns unfinished production and federation work.
Two packages, one server #
The Cargo workspace contains the cpds server package and the small
cpds-protocol library package. The server
depends on the protocol package, never the reverse. Its API validates record
shape, turns JSON $link/$bytes forms into native DAG-CBOR values, and computes
content identifiers. It owns no HTTP, account, SQLite, filesystem, or network
policy and can be tested independently.
The rest is organized into modules, not a crate per layer. Record writes, repository blocks, blob-reference indexes, account state, and durable events must share transaction rules. Artificially independent storage or generic CRUD packages would obscure that coupling rather than remove it. Small modules keep responsibilities readable without making every helper a public package API.
Assembly and adapters #
AppState::assemble wires services to a concrete Database and
the narrow BlobStore, EventSink, and ClientRegistry interfaces. Services own
authorization and orchestration; HTTP handlers translate requests and errors;
persistence modules own SQL and transaction boundaries. Database code does not
call HTTP handlers, and blob adapters do not decide OAuth or record visibility.
The ordinary HTTP test harness calls this same assembly function with real
in-memory SQLite, MemoryBlobStore, a static OAuth client, and
RecordingEventSink. Separate filesystem, process, and local fake-server tests
exercise behavior that an in-memory router cannot prove.
BlobStore has four operations: check, put, get, and delete. Bytes are
namespaced by owner and CID; MIME type, size, publication state, and references
remain in SQLite. The shared contract test
covers both bundled stores. EventSink has check and publish; it is a
best-effort application hook, not the federation log.
Public repository transactions #
repository/records.rs validates identifiers,
ownership, swaps, record data, and uploaded blob references. Typed mutations
retain create/update/delete intent throughout ordered batch planning.
repository/commit.rs applies them through
ATrium's MST and stages content-addressed blocks before asking
database/repo.rs to publish them atomically.
The transaction-local block overlay and CID-verified reads live separately in
database/blockstore.rs; that adapter stages
bytes but does not decide which record changes may commit.
One successful public mutation transaction contains the final signed head,
record index, blob-reference index, repository blocks, ordered operations, and
durable sync-event metadata. An applyWrites batch produces one signed commit,
not a series of hidden intermediate commits. The event proof contains newly
written blocks still reachable from the final root. Compare-and-swap checks
prevent a concurrent head or record change from being silently overwritten.
ATrium owns standard MST and repository machinery. CPDS owns staging, signing orchestration, validation, resource limits, and the SQLite commit. Opening an ATrium repository alone is not verification: CPDS also checks signatures, content hashes, revision metadata, and agreement with its SQL record index.
repository/export.rs exports verified local
repositories as bounded CAR archives. Full exports, incremental exports,
record proofs, and requested-block exports have different selection rules; a
shared block table is not permission to return arbitrary known blocks. Remote
CAR ingestion remains outside the implemented public surface.
Public data and Spaces are different stores #
Public records and alpha Space records have separate tables, authorization, revision histories, and commit formats. They share only appropriate mechanisms: canonical record encoding, blob bytes/metadata, identifier checks, identity resolution, and cryptographic dependencies. A Space is not a public repository with an extra access flag.
space/commit.rs implements the alpha LtHash-based
deniable commit, while public repositories use repository-v3 signed MST commits.
Space CAR recovery carries its signed view and index as two roots. These formats
are deliberately not forced through one generic repository abstraction.
Blob visibility follows references. A public download requires a current public
record reference. A Space download first requires authorization for the selected
Space repository and a matching space_record_blobs index entry. Merely knowing
a CID or uploading bytes grants neither public visibility nor Space access.
Space responses and errors are no-store; only successful public blob and
well-known responses receive the public cache policy.
Identity and authentication #
IdentityService centralizes handle and DID resolution.
ATrium provides protocol models and resolver building blocks; CPDS retains DNS
conflict handling, did:web path/port mapping, transport policy, document-ID
checks, and the required handle backlink. Local startup uses the configured
development identity. Production requires bidirectional network verification
before listening and periodically revalidates it. A confirmed mismatch stores
handle.invalid with an identity event; inconclusive transport failures preserve
the last confirmed state.
OAuthService implements authorization codes with exact
registered redirects, S256 PKCE, Argon2 account authentication, and hashed opaque
credentials. Redeeming the code and inserting the access token is one SQLite
transaction: failed token storage must not consume the code. The static registry
is intentional; this is not the complete AT Protocol OAuth server profile.
Space delegation and DPoP-bound credentials belong to
space/auth.rs, service-token/notification transport to
auth/service.rs, and client attestations to
auth/attestation.rs. Shared outbound checks
live in identity/http.rs. Space DPoP support must not be
confused with unimplemented OAuth DPoP support. The configured public origin,
not forwarding headers, determines request-URL bindings.
Durability, replay, and recovery #
repository/stream.rs rebuilds retained SQLite
events into generated ATrium messages. http/subscription.rs
owns WebSocket framing, admission, backpressure, and shutdown. A disconnected
consumer resumes from its last durable sequence cursor. A cursor older than
retention receives OutdatedCursor and requires repository resynchronization.
Failure of the optional event sink never turns an already committed write into
an apparent failure; doing so would encourage unsafe retries.
repository/maintenance.rs separates bounded
readiness checks and garbage-collection passes from exhaustive startup storage
verification. Blob publication reserves metadata before durable byte publication.
Collection durably claims aged unreferenced objects before deletion and considers
both public and Space references. Block collection preserves current repository
graphs and blocks required by retained events.
The process has five explicit modes: serve, verify, backup, request-crawl, and
set-account-status. Verification and operator modes require existing storage and
do not listen. Backup holds an SQLite write barrier across a native database
snapshot and CID-verified blob copy; its manifest omits passwords and signing
keys. Crawl requests deliberately do not open storage. The reversible deleted
hosting state suppresses access; it does not erase stored bytes or backups.
SQLite currently uses one mutex-protected connection. This makes transaction ownership explicit but serializes database work. Synchronous SQLite and password hashing also remain blocking work; async function signatures and HTTP deadlines do not by themselves preempt them. Do not infer throughput or isolation from the router's concurrency limits. That tradeoff and the remaining operational gates stay visible in the production plan.