Module Map
DraftThe code is the source of truth for module layout; this page fixes only the crate graph, the boundaries between crates, and the bounded E2E case list.
Crates
Section titled “Crates”capsule-core (no internal deps)capsule-i18n (no internal deps)ptpip-rs (no internal deps)capsule-library -> core, ptpip-rscapsule-sdk -> core, library, i18ncapsule-wasm -> corecapsule-core-ffi -> core, library, sdkcapsule-server -> core, i18n (dev: sdk, library, wasm, server-migration; never linked)capsule-cli -> core, library, sdk, i18n, cli-entity, cli-migrationcapsule-e2e -> server, sdk, cli, library, core, wasm (tests only)xtask -> core, i18n| Crate | Responsibility |
|---|---|
capsule-core | Stateless: cryptography and MLS authority, canonical CBOR, sidecar and CRDT logic, validation, import grouping and upload policy, media, LQIP, sharing and drop crypto, enrollment payloads, ML orchestration |
capsule-library | All local state: the SQLite index, the on-disk library and its lock, the Workspace, import scanning, planning and execution, sidecar and album-store I/O, backup I/O, file-path EXIF reading, desktop TPM signers |
ptpip-rs | The in-house PTP/IP (ISO 15740) initiator behind camera import: packet codec, datasets, a blocking session and the vendor-extension seam |
capsule-i18n | Canonical ICU catalogs and runtime localization (Internationalization) |
capsule-sdk | Spargen-generated REST client plus Capsule’s orchestration: auth refresh, resumable upload, sync, recovery, version negotiation, LAN peering |
capsule-wasm | Browser sealing surface (share-link open, guest-drop seal) over capsule-core with default features off |
capsule-core-ffi | The app-facing UniFFI staticlib |
capsule-server | The Kynos REST/OpenAPI application (API Surfaces) |
capsule-cli (+ entity, migration) | The CLI and its local SQLite persistence; network calls ride the SDK |
capsule-e2e | The cross-crate cases below, against the real server composition |
xtask | Codegen, i18n generation, architecture checks |
capsule-core-swift, capsule-core-kotlin | Platform harnesses and HardwareSigner implementations (Secure Enclave, StrongBox) |
capsule-web, capsule-swift, capsule-android | The clients (Clients) |
Stateless Core
Section titled “Stateless Core”capsule-core is a library of pure functions over values. It holds no connection, file handle,
cache, global or background task; it reads no environment, clock or filesystem. Time, randomness
and storage reach it through Clock, Rng and Store traits that the caller implements, so
every core behaviour is testable with fixed inputs. Exceptions, each confined to one module:
| Exception | Why |
|---|---|
OS CSPRNG behind crypto::rng | Key material must never come from an injectable, and therefore replaceable, source |
catch_unwind in media::decode | A decoder panic on hostile input must become a typed error, not abort the caller |
Anything else that needs state belongs in capsule-library (local) or capsule-sdk (network).
cargo xtask architecture-check holds the boundary: capsule-core takes no normal dependency on
a stateful crate (rusqlite, sea-orm, sqlite-vec, sqlx, walkdir), and no file under
capsule-core/src names std::fs, std::env, rusqlite or sea_orm, or calls a Path filesystem method
(.exists(), .is_file(), .is_dir(), .try_exists(), .read_dir(), .canonicalize(),
.read_link(), .symlink_metadata()). The scan is textual, so .metadata() on a Path is a
known blind spot (the RAW decoders have an unrelated metadata()); review catches it. Outside
test code (tests.rs files and tests/ directories) it also rejects Uuid::now_v7,
Uuid::new_v4, SystemTime::now, Timestamp::now and Instant::now: every UUIDv7 core mints
comes from an IdSource in capsule_core::ambient, over the caller’s Clock and Rng, and
capsule-library::ambient supplies the system clock and OS random source.
GraphQL and gRPC are retired
(ADR-0001);
legacy-review/ is not buildable.
Server Modules
Section titled “Server Modules”One Kynos application of cohesive internal modules, not separate services: routes/ carries the
HTTP surface; auth, enrollment, directory, upload, blob, serve, verify,
attestation, gc, scrub, sync, album, share, drop, escrow, federation, quota,
moderation, store and problem carry the behaviour, each owned by its namesake design doc. Every
store adapter runs one conformance suite, which is what lets the in-memory double be trusted
(Server Filesystem — Required Services).
Authentication and upload state are separate typed ports; no generic CAS, transfer or TTL library.
Client Boundaries
Section titled “Client Boundaries”| Boundary | Decision |
|---|---|
| REST client | Spargen-generated from the checked-in OpenAPI 3.2 document |
| Namespaces an app links | Exactly two, capsule_core_ffi and capsule_sdk, both inside the capsule-core-ffi staticlib (two Rust staticlibs cannot share a binary). Hardware seams reach Swift/Kotlin as capsule_sdk foreign traits. ADR-0005 |
| Workspace verbs over FFI | Orchestration only: each verb is one call into the capsule-library Workspace, which leaves every cryptographic step to capsule-core |
| Media | Rawshift through capsule-core::media; Chromahash imported directly |
| Alerts | Classes and predicates in capsule-core::notify; scheduling and presentation are native (Notifications) |
E2E Test Surface
Section titled “E2E Test Surface”The surface is bounded: adding a case means justifying why the cross-module surface is
irreducible, and each case stays backed primarily by unit and adapter-contract tests. Code names
the case it covers (rg "E2E case N").
- Auth → sync → client-side library query. Sign in, apply the sync feed, a local
library.sqlitequery lists the expected albums. - Import + upload + finalize. Scan → plan → execute → upload → finalize; the blob is at its content address and the index row is uploaded.
- Sync feed pickup. Device A uploads; device B’s feed advances and B fetches metadata and, per scope, the original.
- Federation cross-server pull. A capability lets Bob’s server pull Alice’s metadata and blobs and Bob’s client renders them.
- LAN peering A→B. Discovery → TLS → delta artifact → byte-equal libraries.
- Backup → restore on a fresh device. Passphrase and escrow bootstrap; every asset present and verifiable.
- Full lifecycle. Create → update → trash → restore → delete → purge after retention; the
provenance chain advances and the server refuses purge before
retention_until. - Album upgrade ceremony. Quiesce → drain → tombstone → fork → replay, including one resume from a mid-ceremony crash.
- Cross-version protocol gate. An out-of-range
protocol_versionupload gets426and an actionable error. - Model regen after version bump. Stale embeddings are excluded from queries until regen replaces them.
- Server crash mid-finalization. A crash between blob rename and index commit leaves the
session
FailedProcessing, the assetPending, and nothing referencing the blob; an orphaned blob is permitted and collected. The process-restart variantSIGKILLs the CLI or the server at seeded points and reruns the phase until every asset is durable exactly once and no index row lacks its blob, and the restore is byte-equal. It runs armed during import, push and restore. - Cross-device enrollment. A authorizes B over a verified channel, cross-signs it, B joins each album’s MLS group and the libraries match; includes one MITM-on-relay abort.
- Web drop → adopt. A WASM client seals a drop; the owner’s native client rewraps and adopts
it, and a second device
verify_asset-accepts it. - Share link over TCP. A link issued by the CLI opens from the URL alone through the
capsule-wasmentry points, and revocation turns it into the public path’s404. - Upload delegation. A hands a sealed bundle to B, B uploads it, A releases the original only after verifying the receipt against its own hash; B’s death mid-upload makes A re-queue it.