Client Filesystem
DraftClients hold keys, so a client stores plaintext. A library routinely holds assets whose original is server-only, depending on the sync scope, so the layout represents an asset with or without its original. The layout is a contract: rebuild assumes exactly these names. The offline-first requirements it serves are owned by Local Gallery.
Desktop Library Layout
Section titled “Desktop Library Layout”{library_root}/├── media/{YYYY}/{YYYY-MM}/│ ├── {uuid}.{ext} # original (plaintext; absent if not synced locally)│ ├── {uuid}.cbor # canonical signed metadata sidecar│ └── {uuid}.provenance.cbor # append-only signed provenance chain├── cache/ # thumbnails/, meta/, transcodes/ — sharded {uuid[0:2]}/{uuid[2:4]}├── index/library.sqlite # rebuildable query + vector index└── .library/ ├── version, config, lock ├── docs/{albums,stacks}/{uuid}.cbor # signed album and stack label records └── quarantine/{uuid}.{ext} + {uuid}.reason.jsonmedia/is canonical: original, sidecar, and provenance chain. The library MUST be reconstructible from these three files alone. Stems are UUIDv7; extensions lowercase. The provenance file is never deleted, so a hard-deleted asset leaves a tombstone-with-history.- A trashed asset keeps its original, sidecar and chain in its
media/bundle until purge: trash is a signeddeleterecord in the chain, not a location, so no file moves and a rebuild derives Trashed from the chain (Deletion State Machine). Purge removes the original, the sidecar and everycache/derivative, and leaves the chain. A bundle holding only a chain headed by adeleteis the asset’s peering tombstone; no file outsidemedia/records a purge, so a rebuild reproduces Purged from the chain alone. - The date bucket is fixed at import from the capture timestamp then known. A later capture-date correction does not move the bundle; the sidecar is authoritative and maintenance repairs drift opportunistically.
cache/is derived and deletable at any time; never a source of truth. Formats: Thumbnails.index/library.sqliteis a query cache over the sidecars, thesqlite-vecindex for AI, and the substrate for view albums. It is migrated forward in place, not dropped: a rebuild cannot recover projections never written to disk (Versioning)..library/: thelockfile MUST prevent two processes opening one library.quarantine/preserves irreplaceable bytes that failed validation, verbatim, with the rejection reason (Quarantine Surfaces). Aversionbump rebuilds derived structures only and never touches canonical files.docs/holds each album’s and stack’s signed label record as canonical CBOR, plaintext at rest like a sidecar and sealed under the album’s AMK whenever it leaves the device.
Mobile Clients
Section titled “Mobile Clients”Android and iOS use the app’s private sandbox with the same logical model. Rebuildable derivatives MUST NOT be placed in OS-managed cache locations: the OS evicts indiscriminately, and an expensive-to-regenerate thumbnail is not disposable.
Local Index Staleness
Section titled “Local Index Staleness”SQLite may lag the filesystem. The client checks file existence before acting on a row and rebuilds from sidecars on structural inconsistency (Maintenance).
Space Recovery
Section titled “Space Recovery”Capsule reclaims space itself. Eligible: cache/ and fetched-but-unpinned originals. Never eligible: canonical media/ files the device holds as source of truth, sidecars, provenance chains, and library.sqlite. An evicted representation re-fetches transparently via tiered fetch.
Automatic cache management
Section titled “Automatic cache management”The reclaimable set is held within a user-configurable budget (capsule-library::library::cache_sweep):
- Viewing stamps a last-access time; eviction is least-recently-accessed first.
- At equal recency, evict original → preview → thumbnail. The metadata tier (sidecar + LQIP) is never reclaimed, so an asset always stays listable.
- Pinned representations and device-owned originals are exempt from automatic eviction.
The budget follows the connection class (capsule-sdk::cache_budget). The user’s base budget (default 2 GiB) is retained as-is on unmetered, doubled on metered and constrained, tripled on adverse and quadrupled on offline, because re-fetching an evicted representation grows costlier in that order. Every class but unmetered also defers bulk transfers: a push on such a class runs staged whatever the device’s upload policy, so the index tier still publishes each asset while preview and original wait for an unmetered link. The client samples the class through a port before each sweep and each transfer, and re-plans when it changes; a desktop with no OS signal reports unmetered. The base budget is a user setting (capsule cache budget --set); capsule cache sweep runs the sweep under the current plan.
Releasing a device-owned original (via the user-driven path or streaming import) MUST pass the verify-before-destroy gate. Once released it is an ordinary server-only, re-fetchable asset.
User-driven reclamation
Section titled “User-driven reclamation”The client surfaces the largest consumers and lets the user delete selectively, including pinned content. It never touches canonical media/ files, and releasing a device-owned original is gated as above.
Validation
Section titled “Validation”- Init/open round-trip (unit). Directories and
version/configpresent; re-open is idempotent. - Date bucketing (unit). Asset lands in
media/{YYYY}/{YYYY-MM}/; unexplained drift triggers repair. - Lock contention (smoke). A second process is refused with a structured error.
- Sandbox placement (smoke, per platform). Library in app-private storage; survives cold start.
- Index rebuild (smoke). Drop
library.sqlite; re-open; queries match. - Eviction (unit). LRU order, tier order at equal recency, recency promotion, pin exemption; no canonical file touched; metadata tier never reclaimed.
- Connection-class budget (unit). Each class yields its budget and bulk deferral; a class change mid-session re-plans without evicting pinned or device-owned originals.
- Release gate (smoke). Non-
durableverdict retains the original and surfaces it;durablereleases.