Skip to content

Swift

Stable

Generated from capsule-sdk/bindings-surface.json, the dump mise run gen-bindings writes from the capsule_sdk Swift sources uniffi 0.31.1 generates (capsule_sdk.swift), and checks with --check in the Rust gate. It lists every declaration the namespace exports; the generated source also carries the converters and checksums uniffi needs internally, which are not a caller’s surface. To change a description on this page, change the Rust doc comment it comes from and regenerate; this file is build output. Which SDK serves which platform is the SDK overview.

public func syncedHistorySources(manifestCbor: Data, history: [Data]) throws -> FfiHistorySources

Name what adopting an asset from its history fetches: the live metadata blob and the original, read off the asset’s latest record manifest_cbor (a feed entry’s) and history, the records before it that FfiSession::fetch_provenance_history returned.

An album page — the one a writer member’s device reads — serves a delete with no metadata blob and no blob, so a device that first sees the asset there has only its records. The live metadata blob is the newest record that binds one, and the delete commits to the original; the server serves both to the asset’s readers until its retention window closes. Fetch them with FfiSession::fetch_metadata_blob and FfiSession::fetch_blob_to_path, set the entry’s metadata_blob to the first, and hand the second to FfiWorkspace::open_synced_history: the asset is held in this device’s trash and its trash-restore can be signed. An entry that names its metadata blob (the owner’s own feed) agrees with this answer.

Pure and offline. Nothing is trusted for being named: the adoption binds the metadata blob and the original to the records, and refuses what does not.

FfiError::Sync when a record does not decode or none binds a metadata blob.

public func embeddedPreview(originalPath: String, ext: String) throws -> FfiEmbeddedPreview?

Read the embedded preview of the original at original_path, whose extension (without the dot) is ext.

None when the original is not a RAW, carries no preview, or carries one that does not read within the decode budget: each leaves the rung to the next source, so none is an error. The reason is logged.

This reads the whole original into memory, because the extraction’s IFD bounds walk and its stated-length guard run over the full file before the RAW reader does. Call it once per asset and keep the FfiEmbeddedPreview it returns, rather than once per render.

FfiError::Workspace when the file cannot be read.

public func renderStep(request: FfiRenderRequest, shown: FfiRendition?, local: FfiLocalHoldings, fetchable: [FfiRepresentation]) -> FfiRenderStep

One step of the render ladder (crate::fetch::render_step): what to show now and what to obtain next. shown is what the surface shows, None when it shows nothing yet or is falling back below what it showed; fetchable is what the server lists for the asset.

public func evaluateAlerts(input: FfiNotifyInput, now: String) throws -> [FfiAlert]

Every alert that is true at now, in delivery order.

Pure and offline: no network call, no library open, no clock read — now is the caller’s, so the same input always gives the same answer and an app can drive it from a test clock.

FfiError::InvalidArgument if now or any instant in input is not RFC 3339, or if suppressed_until names something that is not an alert class.

public func nextAlertDeadline(input: FfiNotifyInput, now: String) throws -> String?

The earliest instant in pre_arm_deadlines (RFC 3339), or None when there is nothing to arm.

For a host that can hold only one timer. An app that schedules per class wants pre_arm_deadlines: the minimum discards the later class’s alarm, and on a device the app never runs on again that alert is simply lost.

As evaluate_alerts.

public func preArmDeadlines(input: FfiNotifyInput, now: String) throws -> [FfiClassDeadline]

The instant to arm a local notification for, per class — the call an app schedules its UNCalendarNotificationTrigger / AlarmManager alarms from.

Reconcile your timers against the result: a class present here should hold exactly one alarm at the returned instant, and a class absent from it should hold none. Recompute after any state change; that is the whole of the arm / re-arm / cancel rule on the client side.

Only the two classes whose deadline a device can compute alone ever appear. The other three depend on server state and surface at next app launch — a real gap the design accepts.

As evaluate_alerts.

public func p256HardwareDekRoundTrip(hardware: FfiHardwareKeyAgreement, keyAlias: String, mlSeed: Data) throws -> Bool

Smoke the hardware-bound P-256 device encryption key across the FFI seam: enroll a key-agreement key in hardware under key_alias, publish the hybrid public key, encapsulate a fresh shared secret to it in software (the sender side), then decapsulate through the element’s ECDH. Returns true iff the two secrets match — proving the element’s ECDH composes into the hybrid KEM without the private scalar leaving hardware. ml_seed (32 bytes) is the software-sealed ML-KEM-768 half.

The DEK is not yet wired into workspace creation, so this focused round-trip is the composition’s on-device check — the DEK analogue of FfiWorkspace::create_with_p256_hardware_signer. Every step is one capsule-core call.

open class FfiCapsuleClient: FfiCapsuleClientProtocol, @unchecked Sendable

The unauthenticated entry point over the SDK’s auth + upload endpoints. Turns credentials into an FfiSession handle; the protocol pin and endpoints are captured once here and reused by every session this client mints.

public convenience init(authBaseUrl: String, uploadBaseUrl: String, protocolVersion: String, cohortHash: String?) throws

Build a client.

  • auth_base_url is the auth endpoint root (e.g. https://api.example.com/auth).
  • upload_base_url is the upload endpoint root (POST target; chunk/HEAD hang under {upload_base_url}/{id}).
  • protocol_version is the pinned protocol date (YYYY-MM-DD).
  • cohort_hash is the optional advisory device-cohort digest; None sends nothing (the server behaves identically).
func login(email: String, password: String) async throws -> FfiLoginOutcome

Authenticate with email + password, returning an authenticated session handle.

func register(email: String, password: String) async throws -> FfiSession

Create an account and return an authenticated session handle (the server issues tokens on registration). An address and a password, and nothing else: the server takes nothing else. A display name is a fact about a person, and the profile surface that would hold one is owed rather than assumed.

func verifySecondFactor(mfaToken: String, totpCode: String) async throws -> FfiSession

Complete a sign-in with the code an authenticator app is showing.

mfa_token is what FfiLoginOutcome::SecondFactorRequired carried. It is good once and for five minutes.

open class FfiSession: FfiSessionProtocol, @unchecked Sendable

An authenticated session handle. Owns the SDK token store internally (auto pre-flight refresh, single-flight, 401-retry-once); no token ever crosses the FFI boundary. Every flow rides this handle.

func escrowGet(apiBaseUrl: String) async throws -> Data

Fetch this account’s escrow blob (GET /v1/auth/escrow) as opaque canonical CBOR — the bytes FfiWorkspace::verify_escrow_blob checks and a recovery flow unwraps.

No escrow enrolled yet is FfiError::Escrow carrying error.escrow.not_stored — the code that separates “set up a recovery key” from “we could not read the one you have”. A refused credential is FfiError::Auth.

func escrowPut(apiBaseUrl: String, blob: Data) async throws -> FfiEscrowStored

Store or replace this account’s master-key escrow blob (PUT /v1/auth/escrow). blob is the opaque canonical CBOR FfiWorkspace::escrow_blob minted — the master key itself never crosses this boundary in either direction.

Single active escrow: the server replaces any prior blob in the same transaction, so the secret a rotation retired unwraps nothing.

api_base_url is the API root the session authenticates against (the per-call endpoint convention this surface already uses for sync_pull). A URL operation paths cannot hang off is FfiError::InvalidArgument; a refused credential is FfiError::Auth, so a caller re-authenticates rather than retrying; everything else is FfiError::Escrow with the server’s error.escrow.* code when it sent one.

The answer says what the store did: FfiEscrowStored::replaced separates “recovery set up” from “recovery key replaced” (the previous secret has stopped working).

func fetchBlobToPath(blobBaseUrl: String, hash: String, size: UInt64, path: String) async throws

Fetch the blob at content address hash (size bytes, as the sync feed’s FfiBlobRef declares) into the file at path, created or truncated.

The blob streams to disk in bounded Range windows, so memory is bounded by one window whatever the blob’s size, and its content address is checked as it lands. On any error the file is removed: a path this returns Ok for holds exactly the verified ciphertext. A session that cannot authorize the fetch is FfiError::Auth, so the app signs in again; every other failure is FfiError::Fetch.

blob_base_url is the root the blob route hangs off ({blob_base_url}/blob/{hash}), the server’s /v1 root.

func fetchDeviceDirectory(apiBaseUrl: String, userId: String, pinnedUserIk: Data) async throws -> Data

Fetch a user’s signed device directory, verified under pinned_user_ik (the bytes FfiWorkspace::user_ik_public returns) before it is handed back. A document that does not verify is an error, never a return value.

func fetchMetadataBlob(blobBaseUrl: String, hash: String) async throws -> Data

Fetch the metadata blob at content address hash, which a sync feed entry names by address only (its metadata_blob): the feed lists no size for it, so it is read in bounded Range windows until the server’s answer ends, and returned whole once it hashes to hash. This is the bytes FfiSyncHead::metadata_blob carries into FfiWorkspace::open_synced_original or FfiWorkspace::open_synced_history.

A sealed sidecar is a few KiB; a blob that grows past MAX_METADATA_BLOB_BYTES is refused rather than buffered without bound. On a delete in trash the server serves the asset’s live metadata blob to its readers until the retention window closes, so an asset first seen at its delete can be adopted and restored; where the entry names no metadata blob (an album page’s tombstone), synced_history_sources names its address.

blob_base_url is as for fetch_blob_to_path. A hash that is not a lowercase hex SHA-256 is FfiError::InvalidArgument and reaches no network; a session that cannot authorize the fetch is FfiError::Auth; a blob the server refuses, that does not hash to its address, or that runs past the bound is FfiError::Fetch.

func fetchProvenanceHistory(blobBaseUrl: String, manifestCbor: Data) async throws -> [Data]

Fetch the earlier provenance records of the asset whose latest record is manifest_cbor (a feed entry’s): each record the one after it names by prior_provenance_hash, back to the asset’s create, from the blob route the server serves an asset’s superseded records on. Returned oldest first, each the exact bytes the server holds, checked against its content address; empty for a create.

For an asset this workspace does not hold, first seen past its create — an entry FfiWorkspace::apply_sync_entry_from_file quarantines as Rejected.ForgedChain when the app’s catalog holds no head for it, or applies without adopting (FfiAssetFacts::adopted false) when it does — hand the records to FfiWorkspace::open_synced_history, which verifies them in order and holds the asset from its create. Nothing is verified here: the walk only follows links.

An asset first seen at its delete is fetched the same way while it is in trash: the server serves a trashed asset’s records, and its live metadata blob and original, to its readers until the retention window closes, so the adopted asset can be restored. An album page’s tombstone names neither blob; synced_history_sources names both from these records.

blob_base_url is as for fetch_blob_to_path. A record the server refuses (a deleted asset’s past its retention window, a purged asset’s, or one the caller may not read) or that does not hash to its address is FfiError::Fetch; a session that cannot authorize the fetch is FfiError::Auth; a record that does not decode, or a chain longer than MAX_HISTORY_RECORDS, is FfiError::Sync.

func isAuthenticated() async -> Bool

Whether the session currently holds tokens (false after logout).

func logout() async throws

Revoke the session server-side and clear the local store (idempotent).

func publishDeviceDirectory(apiBaseUrl: String, directoryCbor: Data, userIk: Data) async throws -> UInt64

Publish this device’s signed device directory, returning the directory_version the server now stores. directory_cbor is the opaque document FfiWorkspace::signed_device_directory produced; it travels verbatim, because re-encoding it would detach it from the signature it carries. user_ik is the account’s user identity public key the document is signed under — the bytes FfiWorkspace::user_ik_public returns — which the server anchors on first publish and verifies every publish against.

Publish when the document changes (a device enrolled or revoked). The version must advance — republishing an unchanged document answers the version-conflict code.

func pushAsset(workspace: FfiWorkspace, assetId: String) async throws -> FfiPushReport

Push one sealed asset of workspace — every blob of its bundle, in ladder order (provenance and metadata, then derivative manifests and derivatives, then the original) — driving capsule_sdk::push::push_bundle inside the SDK.

The original never crosses the boundary: its ciphertext is re-derived from the plaintext on disk one upload chunk window at a time, so memory is bounded by the transfer window whatever the original’s size. The workspace lock is held only while the bundle is built, never across the network.

Every blob the reported path admits is driven (set_path); the tiers it holds back are counted in FfiPushReport::deferred and wait for the next push. A blob the server already holds answers duplicate_blob and comes back FfiUploadOutcome::AlreadyStored, so pushing twice is safe. The album must already be provisioned on the server.

A create the server refused is re-sealed and retried once (issue #822). When the album’s epoch moved past the one the asset was sealed under, the server refuses its create for good; if this device holds a newer epoch, the asset is re-sealed at it (Workspace::reseal_create) and pushed again, once. A second refusal fails the push. When this device holds no newer epoch the push fails with FfiError::Upload carrying the server’s code and a detail saying there is no newer epoch to re-seal at, and the asset stays local. That detail names no missing key: the refusal may have had nothing to do with the epoch (a revoked device, withdrawn write access), or another device rotated the album and its key has not reached this one yet.

To push several assets, use push_assets: one call here is one bundle on the server’s lane, so the lane never holds more than one session open.

func pushAssets(workspace: FfiWorkspace, assetIds: [String]) async -> [FfiPushAssetResult]

Push several sealed assets of workspace in one call, returning one FfiPushAssetResult per id, in the order of asset_ids.

Every asset is pushed exactly as push_asset pushes one — its original streamed in bounded windows, the tiers the reported path holds back counted in FfiPushReport::deferred, and a create the server refused re-sealed and retried once — but the assets share one run of the server’s lane: up to W of them have their sessions open at once, every range any of them sends reserves against one device-wide byte budget, and their retries spend from one lane budget. Calling push_asset concurrently instead gives each call its own budget, so the budgets add up rather than being shared.

The ids are pushed in batches of a bounded size, so a list of any length holds only one batch’s bundles in memory at a time. One asset’s failure is its own (FfiPushAssetResult::Failed, carrying the error push_asset would have returned for it): the others still push, and the call itself never fails. An id repeated in asset_ids pushes once, at its first occurrence; each later occurrence is FfiPushAssetResult::Failed with FfiError::InvalidArgument.

The adverse-link detector hears each batch’s outcomes when that batch’s run ends, so a link that degrades mid-batch re-plans the push from the next batch on.

func setPath(snapshot: FfiPathSnapshot) -> UInt64

Report the path as the app’s OS listeners see it now, and return its path generation.

Call it whenever a listener fires. The generation advances only when reachability, the interface or the cost changes; it then drops this session’s pooled connections, forgets the resets and stalls seen on the old path, and cancels — within one scheduling tick — any push work the new path forbids, which re-plans and resumes. Power, thermal and background-deadline changes re-plan without a new generation.

func syncPull(apiBaseUrl: String, cursor: Data, pageSize: UInt32) async throws -> FfiSyncPage

Pull one page of the key-free sync feed after cursor from api_base_url (http[s]://host:port). Pass an empty cursor for the first page; persist and pass back next_cursor to advance. The bearer rides the Authorization header (auto-refreshed); it never crosses the FFI boundary.

The parameter is the API base URL now, not a gRPC endpoint: the feed is GET /v1/sync on the same origin as every other call.

func transferEvents() -> FfiTransferSubscription

Subscribe to the step-by-step progress of every upload, push and fetch this session drives from now on, each event stamped with the milliseconds since this call on a monotonic clock.

Progress is advisory: a transfer never waits on its observer, so a subscriber that falls behind reads FfiTransferFeedItem::Lagged with the count it missed. The subscription ends once this session is dropped.

func upload(request: FfiUploadRequest, data: Data) async throws -> FfiUploadOutcome

Upload a full ciphertext blob (data) under request, driving the adaptive, resumable protocol with the code-driven recovery matrix.

data crosses the boundary whole, so this is for blobs an app already holds in memory. To push a sealed asset, original included, use push_asset, which never holds the original whole.

func uploadStatus(sessionId: String) async throws -> FfiUploadStatus?

Query an upload session’s progress (HEAD /upload/{id}). None means the session is gone (discarded/expired/never-existent).

func applyLifecycleOp(apiBaseUrl: String, workspace: FfiWorkspace, record: Data, serverHead: Data?) async throws -> FfiLifecycleOpOutcome

Apply one signed lifecycle op record — a delete or a trash-restore, as the opaque canonical CBOR FfiWorkspace::provenance_head, FfiPurgeOutcome::reissue_record and FfiWorkspace::purged_record carry — to its album’s ops surface on api_base_url (http[s]://host:port, the root sync_pull takes).

The record must be one workspace holds: its device signature verifies under the workspace’s device directory, and it is a record of the workspace’s chain for its asset (live, trashed, or kept by a purge). The server cannot verify signatures, so a record that is not this workspace’s own is refused here rather than becoming the asset’s chain head there.

server_head is the sync feed’s evidence for the asset: the manifest_cbor of the latest entry listing it (empty for an entry that carries none), or None when the feed does not list it. With None nothing is sent and the answer is FfiLifecycleOpOutcome::NotIndexed: the server never sees a lifecycle op for an asset it has not indexed.

Idempotent: a record the server already applied answers Applied with replayed set. A refusal, or a server that could not be reached, is FfiLifecycleOpOutcome::Failed; a session the server did not accept is FfiError::Auth.

FfiError::InvalidArgument for bytes that are not a lifecycle op record (over the bound, not canonical, not a delete or trash-restore, not signed by a device of the workspace’s directory, or not on the workspace’s chain for its asset) or an api_base_url that is not an http(s) URL, before anything is sent; FfiError::Auth for a refused session.

func deliverLifecycleAtHash(apiBaseUrl: String, workspace: FfiWorkspace, assetId: String, serverHeadHash: Data?, latestChangedAt: String?) async throws -> FfiLifecycleOpOutcome

Deliver the held lifecycle records of an asset workspace still holds, live or in the trash: every record of its chain after the head the server holds, in chain order, each once the one before it was applied. That is a delete or trash-restore signed while the server had not indexed the asset, or whose send failed, together with any it chains onto that the server never saw. The rule is the CLI’s and a purge’s (plan_delivery).

server_head_hash is the feed’s evidence for the asset, as deliver_purge_at_hash takes it: None, an asset the feed does not list, sends nothing and answers FfiLifecycleOpOutcome::NotIndexed.

latest_changed_at is the latest changed_at (RFC 3339) the caller’s feed drain saw, on the server’s clock, or None when it saw none. It floors this device’s clock (judging_clock), and a stretch in which a trash-restore undoes a delete whose window had closed by that clock is withheld and answers FfiLifecycleOpOutcome::RestoreTooLate: sent, the server would apply the delete and refuse the restore.

Everything is read from the workspace’s chain, so a delivery that failed or was held is retried by calling this again, also after the workspace was reopened. The workspace lock is held only while the chain is read, never across the network, and every record of the stretch is judged as apply_lifecycle_op judges a foreign one before any is sent. A server level with the chain is sent its head alone, which it answers as a replay; a caller that compares the listed head with FfiWorkspace::provenance_head first sends nothing for it. The answer is the last record’s Applied, or the first record that was not applied, whose Failed stops the stretch.

FfiError::Trash with FfiTrashRefusal::NotFound for an asset this workspace does not hold (a purged one goes through deliver_purge_at_hash); FfiError::InvalidArgument for a malformed asset_id or api_base_url, a server_head_hash that is neither empty nor 32 bytes, or a latest_changed_at that is not an RFC 3339 timestamp, before anything is sent; FfiError::Workspace for a chain whose records do not verify under the workspace’s device directory; FfiError::Auth for a refused session.

func deliverPurge(apiBaseUrl: String, workspace: FfiWorkspace, assetId: String, serverHead: Data?) async throws -> FfiLifecycleOpOutcome

Deliver a purged asset’s delete to its album’s ops surface: every record of the chain the purge kept after the head the server holds, in chain order, each once the one before it was applied. After an early purge that is the shorter-retention re-issue (FfiPurgeOutcome::reissue_record) together with any delete it chains onto that the server never saw.

Everything is read from the workspace’s kept chain, so a delivery that failed or was held is retried by calling this again, also after the workspace was reopened (FfiWorkspace::purged_asset_ids lists what to walk). The workspace lock is held only while the chain is read, never across the network. Every record of the stretch is judged as apply_lifecycle_op judges a foreign one before any is sent, so a kept chain whose records no longer verify sends nothing.

server_head is as apply_lifecycle_op takes it: None sends nothing and answers FfiLifecycleOpOutcome::NotIndexed. The answer is the last record’s Applied — the purge reached the server — or the first record that was not applied, whose Failed stops the stretch.

The held rules are a live asset’s (deliver_lifecycle_at_hash), judged by this device’s clock: a kept chain whose stretch holds a trash-restore undoing a delete the server never saw, whose window has closed, is withheld and answers FfiLifecycleOpOutcome::RestoreTooLate.

FfiError::Trash with FfiTrashRefusal::NotFound for an asset this workspace has not purged; FfiError::InvalidArgument for a malformed asset_id or api_base_url; FfiError::Workspace for a kept chain whose records do not verify under the workspace’s device directory; FfiError::Auth for a refused session.

func deliverPurgeAtHash(apiBaseUrl: String, workspace: FfiWorkspace, assetId: String, serverHeadHash: Data?) async throws -> FfiLifecycleOpOutcome

deliver_purge with the head the feed lists given by its record hash rather than its bytes: the SHA-256 of the latest entry’s manifest_cbor, which is the head record’s hash, so a caller keeps 32 bytes per listed asset instead of the record. Some of an empty vector is an entry that carries no record, and None an asset the feed does not list, exactly as deliver_purge reads its server_head.

As deliver_purge, and FfiError::InvalidArgument for a server_head_hash that is neither empty nor 32 bytes, before anything is sent.

open class FfiTransferSubscription: FfiTransferSubscriptionProtocol, @unchecked Sendable

A live subscription to a session’s transfer events: every step published after it opened.

Obtained from FfiSession::transfer_events. Pull with next until it answers None, which it does once the session that publishes the events is gone.

func next() async -> FfiTransferFeedItem?

The next event, or how many were missed since the last pull; None once the session publishing them is gone, and from then on.

Waits for an event when none is pending. Concurrent pulls take turns: each event is delivered to exactly one of them.

open class FfiWorkspace: FfiWorkspaceProtocol, @unchecked Sendable

A Capsule workspace, callable from Swift/Kotlin: the durable library at a filesystem root plus everything an app does with it.

Workspace mutates through &mut self while uniffi objects expose only &self; the Mutex supplies the interior mutability and serializes concurrent foreign calls.

public static func create(root: String, passphrase: Data, tier: FfiDeviceTier, client: FfiClientBuild) throws -> FfiWorkspace

Enroll: create a brand-new library at root, guarded by passphrase at the Argon2id cost for tier. Mints the account, the device key, and the signed device directory, and seeds the durable album keystore.

client is the app’s own build identity: every manifest this workspace authors then reports client_id/semver+commit rather than the bare capsule-core default.

public static func createWithHardwareSigner(root: String, passphrase: Data, tier: FfiDeviceTier, hardware: FfiHardwareSigner, keyAlias: String, mlSeed: Data, client: FfiClientBuild) throws -> FfiWorkspace

Enroll with a hardware-bound Ed25519 device key: as create, but the classical half of the device signing key is produced by hardware under key_alias over Ed25519 (a 32-byte public key, 64-byte signatures). The post-quantum ML-DSA-65 half is the software-sealed ml_seed (32 bytes).

No shipping secure element holds Ed25519 keys, so this is the software/TPM reference composition; phones use create_with_p256_hardware_signer. It is the same core call (HardwareBackedSigner::enroll) reached through this namespace’s FfiHardwareSigner seam.

public static func createWithP256HardwareSigner(root: String, passphrase: Data, tier: FfiDeviceTier, hardware: FfiHardwareSigner, keyAlias: String, mlSeed: Data, client: FfiClientBuild) throws -> FfiWorkspace

Enroll with a hardware-bound device key: as create, but the classical half of the device signing key is produced by hardware (a native Secure Enclave / StrongBox / TPM element) under key_alias, over ECDSA-P256 — the composition shipping secure elements actually provide, none of which do Ed25519. The post-quantum ML-DSA-65 half is the software-sealed ml_seed (32 bytes).

The published device key carries the P-256 tag and verify_asset dispatches on it. The library records the binding (the alias, ml_seed sealed under the account master key, and the published key), so every later launch reopens it with open_with_p256_hardware_signer on the same key; plain open refuses such a library. An element that refuses enrollment fails this call before anything is written under root.

public static func `open`(root: String, passphrase: Data, tier: FfiDeviceTier, client: FfiClientBuild) throws -> FfiWorkspace

Open the existing library at root under passphrase, restoring its album keys and authorities, every managed asset, and the add_id counter from disk. This is what makes an app relaunch resume rather than re-enroll.

public static func openWithP256HardwareSigner(root: String, passphrase: Data, tier: FfiDeviceTier, hardware: FfiHardwareSigner, keyAlias: String, client: FfiClientBuild) throws -> FfiWorkspace

Open the existing library at root as open does, re-attaching hardware, the element that holds this device’s P-256 signing key. Required for a library made by create_with_p256_hardware_signer: the element is asked for the key under the alias the library recorded, never to make a new one, and the rebuilt key must be the one this device published. An element that lost the key or is unavailable fails the open with FfiError::Workspace rather than letting the library sign with another key.

The library’s record decides, not the caller: a library created with the software key opens on it and hardware is not used. key_alias is used only when root holds a laid-out library with no account yet, whose fresh account is then enrolled under it.

func assetsByCull(flag: FfiCullFlag) throws -> [String]

The managed, non-trashed assets currently carrying flag, sorted. Neutral includes the never-flagged ones.

func cullFlag(assetId: String) throws -> FfiCullFlag

The asset’s current flag: Neutral when it was never flagged, and for an asset this workspace does not hold.

func flagStack(stackId: String, flag: FfiCullFlag) throws -> [String]

Flag every non-trashed member of a stack, one metadata-update each — flagging a collapsed stack flags its members, since a stack stores no flag of its own. Returns the members flagged, sorted; empty for a stack with no members.

func rejectSweep(retainDays: UInt32) throws -> [String]

The reject sweep: move every rejected, not-yet-trashed asset to the trash with a retention window of retain_days days, and return the swept ids, sorted. Each is a signed delete, restorable from the trash until its window closes. A refusal partway is FfiError::Trash; the assets swept before it stay in the trash, and a retried sweep moves the rest.

func setCull(assetId: String, flag: FfiCullFlag) throws

Flag one asset. Neutral clears a prior flag. Signs one metadata-update, stamped with this device and now, so concurrent flags from two devices converge under LWW.

func stackCullState(stackId: String) throws -> FfiStackCullState?

A stack’s cull state, derived from its members on this read. None for a stack with no non-trashed members.

func stackMemberships() throws -> [FfiStackMember]

Every managed, non-trashed asset that belongs to a stack, with its stack, whether it is the cover and its member index, sorted by asset id. An asset in no stack is absent, so a cull pass reads it as a lone asset; a trashed member is absent too, matching the members flag_stack writes.

func purgedAssetIds() throws -> [String]

Every asset this workspace has purged (UUID strings), whose kept chain purged_record reads: the list a relaunch walks to deliver the purges whose delivery failed or was held, with FfiSession::deliver_purge. A purge the server already applied answers as a replay, so walking it again is safe.

func addLabel(target: FfiLabelTarget, label: String) throws -> [String]

Add label to target and return its labels afterwards.

func addRegion(assetId: String, shape: FfiRegionShape, labels: [String]) throws -> FfiRegion

Mark a region of asset_id with its first labels, and return it.

Every label is checked before the region is written, so a refused label leaves no unlabelled region behind; then the shape, then the asset. A refusal writes nothing.

func albums() throws -> [FfiAlbum]

Every album this workspace holds key material for, in the one album order every app shows as is: the album with the most recent visible, untrashed asset first, an empty album by its creation time, ties by ascending id (Workspace::albums).

func applySyncEntry(entry: FfiSyncEntry) throws -> FfiSyncApplyOutcome

Sync-apply: verify one remote feed entry through the verify_asset chokepoint, bind and decrypt its metadata blob, and return either the facts to upsert or a named quarantine/hold verdict.

Never silently drops and never silently accepts: a refused entry is a FfiSyncApplyOutcome::Quarantined the app must surface, not an error that aborts the page. Errors are reserved for a failure of this workspace itself.

An applied record that chains onto an asset this workspace holds is adopted: appended to the asset’s chain with the metadata it binds, written and indexed, and the facts say so (FfiAssetFacts::adopted). A create is adopted only by open_synced_original, which has its original.

func applySyncEntryFromFile(entry: FfiSyncHead, ciphertextPath: String) throws -> FfiSyncApplyOutcome

apply_sync_entry for an entry whose original ciphertext is the file at ciphertext_path — the one FfiSession::fetch_blob_to_path wrote — read in memory bounded by one STREAM chunk whatever the original’s size.

The same chokepoint and verdicts: the file is hashed, and that digest is what the content-integrity step checks against the manifest’s content address, so a file that is not the committed ciphertext is quarantined as Rejected.CiphertextHashMismatch, as apply_sync_entry quarantines those bytes. The ciphertext file is only read.

An applied record is adopted as apply_sync_entry adopts one: a delete, trash-restore or edit of an asset this workspace holds is appended to its chain, written under media/ and indexed, and the facts say so (FfiAssetFacts::adopted).

A malformed album_id or local_chain_head is refused before the file is opened, so a bad argument never costs a read of the original. The file is hashed before the workspace is locked, so other verbs are not held up while a large original is read.

func assetIds() throws -> [String]

Every managed asset id (UUID strings).

func createAlbum(name: String) throws -> String

Create an album (minting AMK_v1, its write-tier and admin keys, and an attested authority, then persisting the keystore). Returns the album id.

func defaultAlbumId() throws -> String

The default album’s id, derived deterministically from the account master key — so every device of this account computes the same value with nothing to synchronize.

func deleteAsset(assetId: String, retainDays: UInt32) throws -> FfiTrashEntry

Move an asset to the trash: sign a delete whose retention window is retain_days days from now, and return its trash entry. An asset already in the trash keeps the window it was deleted with and signs nothing, so a retried delete never extends it.

func deleteRegion(assetId: String, regionId: String) throws

Delete a region. Final: a concurrent or later edit of it on another device does not bring it back once the two merge, and its id is never live again.

func deviceId() throws -> String

This device’s id (UUID string) — the created_by_device of every manifest it authors, and the id to report as the session’s device_id.

func ensureAlbum(albumId: String, name: String) throws -> String

Resolve album_id to the album this workspace already holds, or create it under name. This is the verb first-run wiring wants for the default album: it is idempotent across relaunches, where create_album would refuse.

func escrowBlob(recoverySecret: Data, tier: FfiDeviceTier) throws -> Data

Mint the master-key escrow blob under recovery_secret, as opaque canonical CBOR — the bytes FfiSession::escrow_put stores.

The master key never crosses this boundary: it is wrapped inside capsule-core and only the wrapped blob comes out. recovery_secret is the ≥128-bit secret shown to the user exactly once at first-device enrollment.

func expiredTrash(drain: FfiFeedDrain?) throws -> [FfiTrashEntry]

The trash entries whose signed retention window has closed: the retention sweep’s input, each one ready for purge_asset.

A deadline is judged only after a sync drained the feed to its head, so a restore on a later page lands first: pass that drain as drain, its server_heads covering the whole feed as FfiFeedDrain::server_heads requires. The deadline is judged by the drain’s latest server changed_at alone, never by this device’s clock, and an asset whose delete the server has not indexed (a held delete) never expires. With no drain (None: the sync failed, or this device has no server), or a drain that saw no entry, nothing has expired, and the trash keeps every asset until the user empties it.

FfiError::InvalidArgument for a latest_changed_at that is not RFC 3339.

func labels(target: FfiLabelTarget) throws -> [String]

The labels target carries, sorted. An unknown target is FfiError::Label with FfiLabelRefusal::NotFound.

func openSyncedHistory(entry: FfiSyncHead, history: [Data], ciphertextPath: String, destinationPath: String) throws -> FfiOpenedOriginal

open_synced_original for an asset this workspace does not hold and first sees past its create, with history the records before the entry’s, oldest first from the create — what FfiSession::fetch_provenance_history returns for the entry’s manifest_cbor.

The entry is applied against the last record of history, whatever its local_chain_head says, and its original opened from ciphertext_path as open_synced_original opens one. Then every record of history is verified through the chokepoint in order from the create, the sidecar is opened from the entry’s metadata_blob (the asset’s live one, which the server’s metadata role names: a superseded metadata blob is collected), and the asset is adopted, held from its create with the opened original as its library copy. The facts say whether it was (FfiAssetFacts::adopted); a held asset’s delete, trash-restore or edit then works on this device as on one it imported.

An entry that names no metadata blob or original — a delete on an album page, the surface a writer member’s device reads — is opened the same way once they are fetched: synced_history_sources names both from the entry and history, the fetched metadata blob goes in the entry’s metadata_blob, and the fetched original is ciphertext_path. Left empty, the metadata_blob binds to nothing and the entry is refused.

A history that does not verify, or a live metadata blob that does not bind, is FfiOpenedOriginal::Refused with the verdict that refused it, and destination_path is untouched: the original is placed there only once the asset it belongs to verifies from its create. An empty history is open_synced_original, and so is an entry of an asset this workspace already holds: its history is not read, so none of its records can refuse the entry, and the entry is applied against its own local_chain_head.

As open_synced_original; an adoption whose write failed leaves nothing at destination_path.

func openSyncedOriginal(entry: FfiSyncHead, ciphertextPath: String, destinationPath: String) throws -> FfiOpenedOriginal

Open a synced original from the ciphertext file at ciphertext_path to its plaintext at destination_path, in memory bounded by a few STREAM chunks whatever its size.

The entry goes through the chokepoint as apply_sync_entry_from_file would take it, then the file is hashed while it is decrypted: it opens only if it is the ciphertext the manifest commits to, every chunk authenticates, and the plaintext hashes to the signed sidecar’s digest. The plaintext is written to a hidden staging file beside the destination and renamed into place only once all of that holds, so destination_path is untouched by any other verdict and never holds a partial or unverified original. An existing file there is replaced only by a verified one.

An opened entry is then adopted: a create of an asset this workspace does not hold makes it held, with its chain, sidecar and sealed metadata written under media/, the opened original copied there as its own library copy, and its row and user tags indexed; a later record of a held asset is appended as apply_sync_entry_from_file appends one. The facts say whether it was (FfiAssetFacts::adopted). destination_path still holds the plaintext either way, for the caller to keep or remove.

The workspace stays locked while the original decrypts and is adopted.

FfiError::Workspace when a file cannot be read or written, or the workspace itself fails (an adoption whose write failed included, after the original was placed at destination_path); FfiError::InvalidArgument for a malformed entry or a destination with no file name. A refused entry is a verdict, never an error.

func provenanceHead(assetId: String) throws -> Data

The asset’s head provenance record as opaque canonical CBOR — the exact bytes the provenance blob carries, the feed serves back as manifest_cbor, and a receiving device runs through apply_sync_entry.

The record wraps the head signed manifest with its chain position. That wrapper is not optional: the server’s chain head is the SHA-256 of these bytes while a client’s next prior_provenance_hash is the record’s own hash, so the bare manifest is the one encoding under which no lifecycle op can ever chain. Canonical CBOR is deterministic, so the manifest inside is byte-identical to its own signed bytes — nothing is re-authored and the two signatures still verify over exactly what they covered.

Use signed_manifest when the manifest is what is wanted.

func purgeAsset(assetId: String) throws -> FfiPurgeOutcome

Permanently delete a trashed asset: its original, every derivative and local cache entry, its sidecar and its index row. The provenance chain is kept as the record of the deletion. Before retention_until a signed shorter-retention delete is appended first and handed back in FfiPurgeOutcome::reissue_record to send to the server.

func purgedRecord(assetId: String) throws -> Data?

The signed delete that ended a purged asset, the head of its kept provenance chain, as the opaque canonical CBOR record FfiPurgeOutcome::reissue_record carries. After an early purge it is that same re-issue, so a delivery to the album’s ops surface that failed can be resent from here, also after a reopen (FfiSession::apply_lifecycle_op, or FfiSession::deliver_purge for the whole stretch). None for an asset this workspace has not purged.

func readPlaintext(assetId: String) throws -> Data

The plaintext bytes of a managed asset (what a gallery renders for a locally-held original).

func regions(assetId: String) throws -> [FfiRegion]

The asset’s live regions, oldest first.

func removeLabel(target: FfiLabelTarget, label: String) throws -> [String]

Remove label from target (every add of it this device has observed) and return its labels afterwards. A label the target does not carry writes nothing and answers the unchanged list.

func restoreAsset(assetId: String) throws

Restore an asset from the trash: sign a trash-restore. Refused with FfiTrashRefusal::RetentionElapsed (and the error.upload.retention_elapsed code) at or after the signed retention_until, and with FfiTrashRefusal::NotInTrash for an asset that is not in the trash.

func sealAsset(albumId: String, fileName: String, bytes: Data) throws -> String

Seal + import: take bytes the app already holds (a PhotoKit / MediaStore export), derive the file key, STREAM-encrypt, author and sign the create manifest, seal the signed sidecar into its metadata blob, append the provenance chain, and self-verify through the verify_asset chokepoint. Returns the asset id.

file_name is consulted for its extension and nothing else. One call, one core call: the whole sealing order is capsule-core’s.

func signedDeviceDirectory() throws -> Data

This workspace’s signed device directory as opaque canonical CBOR — the bytes FfiSession::publish_device_directory sends. Until this is published, every asset this device signs is an unknown device to every other device, so first-device enrollment publishes before it uploads.

func signedManifest(assetId: String) throws -> Data

The asset’s head signed manifest as opaque canonical CBOR.

This is a serialization of an already-signed structure, not a re-authoring: the two signatures the manifest carries are covered by these bytes, which is why they travel verbatim and are never re-modeled on any wire.

Not what the sync feed carries — that is provenance_head, the record that wraps this manifest with its chain position. Handing these bytes to apply_sync_entry is refused as malformed.

func trash() throws -> [FfiTrashEntry]

Every asset in the trash with its signed retention window, most recently deleted first.

This is the retention data, not the Recently Deleted view: a surface that shows the trash still opens it behind the SR1 fresh-local-auth gate, as Catalog::query_trash does.

func uploadBlobs(assetId: String) throws -> [FfiUploadBlob]

Everything needed to push one sealed asset, in ladder order: for each blob, the POST /upload body (manifest envelope projected per the server’s invariant-15 rule) and the bytes. Feed each straight to FfiSession::upload.

The index tier (T0) is two blobs — the provenance record then the sealed metadata blob — because the server publishes an asset to other devices only once it holds both roles. Push all of them: an app that stopped after the metadata blob would upload an asset no device, its own included, could ever see.

The original’s ciphertext is re-derived from the manifest’s recorded nonce prefix and gated on the manifest’s own content address, so what reaches the network is what the signed manifest vouches for.

Every blob comes back whole, the original included. To push without holding the original in memory, use FfiSession::push_asset.

Building the bundle records the original’s upload source guard; once every blob is materialised nothing re-derives the original from this bundle again, so the guard is released before the blobs are returned. A later call re-verifies the plaintext against the signed content address and records it afresh.

func userId() throws -> String

The account owner’s user id (UUID string).

func userIkPublic() throws -> Data

This account’s user identity public key — the pin a fetched device directory is verified against.

func verifyAsset(assetId: String) throws -> FfiVerifyOutcome

Run the verify_asset chokepoint over a managed asset, regenerating its ciphertext deterministically. This is the same single chokepoint sync-apply routes through — it is exposed here so an app can re-check its own library (an integrity sweep), never as a second verification path.

func verifyEscrowBlob(blob: Data, recoverySecret: Data) throws -> Bool

Local, network-free check that recovery_secret still opens blob to this device’s master key — the recovery verification cadence’s predicate. blob is the canonical CBOR FfiSession::escrow_get returns.

A false is not necessarily a wrong secret: the cached blob may be stale after a rotation on another device, so refresh it and re-check once before telling the user anything.

public protocol FfiHardwareKeyAgreement: AnyObject, Sendable

The per-platform key-agreement element (Secure Enclave P256.KeyAgreement, StrongBox ECDH, TPM 2.0 ECDH), implemented by native Swift/Kotlin over the uniffi foreign-trait boundary — the ECDH analogue of FfiHardwareSigner, and this namespace’s declaration of capsule-core’s HardwareKeyAgreement contract.

It backs the hardware-bound classical half of the device encryption key: the element holds a static P-256 private key and performs the ECDH, so the scalar never leaves hardware, while Rust drives the software-sealed ML-KEM-768 half and the hybrid combiner. Failures reuse FfiHardwareSignerError, exactly as core’s trait reuses its signer error.

func enroll(keyAlias: String) throws -> Data

Generate (or bind to) the hardware P-256 key-agreement keypair for key_alias, returning its public key as uncompressed SEC1 (0x04‖x‖y, 65 bytes). Idempotent per alias.

func publicKey(keyAlias: String) throws -> Data

The uncompressed-SEC1 P-256 public key for an already-enrolled key_alias.

func keyAgreement(keyAlias: String, peerPublic: Data) throws -> Data

ECDH against peer_public (the sender’s ephemeral P-256 public key, uncompressed SEC1) with the hardware key for key_alias, returning the raw 32-byte shared secret (the big-endian x-coordinate, before any KDF).

func assertNonExportable(keyAlias: String) throws

Non-exportability assertion: a conforming element MUST refuse to reveal the private bytes, returning FfiHardwareSignerError::Exportable if they can be read.

public protocol FfiHardwareSigner: AnyObject, Sendable

The per-platform secure element (Secure Enclave, StrongBox, TPM), implemented by native Swift/Kotlin over the uniffi foreign-trait boundary.

The byte shapes are fixed by the constructor the element is handed to, not by the trait:

  • FfiWorkspace::create_with_p256_hardware_signer and FfiWorkspace::open_with_p256_hardware_signer — the P-256 shape shipping elements actually provide: enroll / classical_public_key return a SEC1 public key and sign_classical a DER-encoded ECDSA signature over SHA-256(msg).
  • FfiWorkspace::create_with_hardware_signer — the Ed25519 shape: a 32-byte public key and a 64-byte signature over msg. No shipping secure element holds Ed25519 keys, so this is the software/TPM reference path.

Either way Rust composes the classical half with a software-sealed ML-DSA-65 half into the published hybrid device key.

func enroll(keyAlias: String) throws -> Data

Generate (or bind to) the hardware keypair for key_alias, returning its public key in the constructor’s shape: a SEC1 P-256 key for FfiWorkspace::create_with_p256_hardware_signer, a 32-byte Ed25519 key for FfiWorkspace::create_with_hardware_signer. Idempotent per alias.

func classicalPublicKey(keyAlias: String) throws -> Data

The public key for an already-enrolled key_alias, in the same constructor-dependent shape enroll returns (SEC1 P-256, or 32-byte Ed25519).

func signClassical(keyAlias: String, msg: Data) throws -> Data

Sign msg with the hardware key for key_alias, in the constructor’s shape: a DER-encoded ECDSA signature over SHA-256(msg) for FfiWorkspace::create_with_p256_hardware_signer, a 64-byte Ed25519 signature over msg for FfiWorkspace::create_with_hardware_signer.

func assertNonExportable(keyAlias: String) throws

Non-exportability assertion: a conforming element MUST refuse to reveal the private bytes, returning FfiHardwareSignerError::Exportable if they can be read.

public struct FfiAlbum: Equatable, Hashable

One album this workspace holds key material for.

MemberDeclarationDescription
albumIdpublic var albumId: StringAlbum id (UUID string).
namepublic var name: StringDisplay name, which lives only in the client’s own state — it never crosses the wire.
public struct FfiAlert: Equatable, Hashable

One alert that is true at an instant.

Never a localized string: class selects the app’s own notification.* catalog key and params are interpolated into it, so a server can neither supply nor influence the words a user reads.

MemberDeclarationDescription
classpublic var `class`: FfiAlertClassWhich alert this is.
severitypublic var severity: FfiAlertSeverityHow prominently to present it.
deadlinepublic var deadline: String?The instant whose passing made this alert true (RFC 3339), for the two pre-armable classes; None for the three whose condition is server-held.
paramspublic var params: [String: String]Catalog parameters — count, days_behind, grace, and the pair recovery_check_due always carries: snooze_budget (available / spent) and recovery (check / rewrap).
public struct FfiAssetFacts: Equatable, Hashable

The verified facts one applied entry contributes, flattened for a catalog upsert.

MemberDeclarationDescription
assetIdpublic var assetId: StringThe asset id (UUID string).
albumIdpublic var albumId: StringThe album id (UUID string).
amkVersionpublic var amkVersion: UInt32The AMK epoch the asset is sealed under.
ciphertextHashpublic var ciphertextHash: StringThe original’s ciphertext content address (lowercase hex).
plaintextSizepublic var plaintextSize: UInt64The original’s plaintext length in bytes.
actionpublic var action: StringThe head manifest’s lifecycle action (create, metadata-update, delete, …).
timestamppublic var timestamp: StringRFC3339 authoring timestamp of the head manifest.
createdByDevicepublic var createdByDevice: StringThe device that authored it (UUID string), resolved in the signed device directory.
provenanceHeadpublic var provenanceHead: DataThe 32-byte provenance head this entry establishes — persist it and pass it back as [FfiSyncEntry::local_chain_head] next time.
metadatapublic var metadata: FfiAssetMetadata?The decrypted, signature-checked metadata. None exactly when the action mints no metadata blob — a delete tombstone or a trash-restore. Those are fully-verified entries that simply carry no metadata, so an app applies the lifecycle change and leaves the row’s metadata as it stands.
adoptedpublic var adopted: BoolWhether this workspace now holds the asset at this entry’s record: it adopted the entry, writing the asset’s chain, sidecar, sealed metadata and original under media/ and its index row and user tags into the library catalog, so the trash verbs and tag edits work on it. When true the catalog already describes the asset and an app writes nothing of its own for it; when false (an asset first seen past its create, a fork, or a verb that applies without adopting) the facts are the app’s to upsert. An asset first seen past its create is adopted from its earlier records: FfiSession::fetch_provenance_history, then [FfiWorkspace::open_synced_history].
public struct FfiAssetMetadata: Equatable, Hashable

The decrypted sidecar’s facts, flattened for a catalog upsert.

MemberDeclarationDescription
sidecarSchemapublic var sidecarSchema: UInt16Sidecar schema version.
contentTypepublic var contentType: StringThe original’s MIME type.
captureTimestamppublic var captureTimestamp: StringRFC3339 capture time the asset is dated by: the user’s correction when one is recorded, else the as-imported value.
widthpublic var width: UInt32?Pixel width, when the sidecar records dimensions.
heightpublic var height: UInt32?Pixel height, when the sidecar records dimensions.
lqipChromahashpublic var lqipChromahash: Data?LQIP chromahash bytes — renderable the instant metadata syncs, at zero extra request.
lqipDominantColorpublic var lqipDominantColor: Data?LQIP dominant colour, as three RGB bytes.
captionpublic var caption: String?The caption LWW register’s current value, if any.
tagspublic var tags: [String]The user-tag OR-set’s current members, sorted.
ratingpublic var rating: UInt8?The rating LWW register’s current value, if any.
hiddenpublic var hidden: BoolWhether the asset is hidden from default views.
public struct FfiBlobManifest: Equatable, Hashable

FFI mirror of BlobManifest — an asset’s blobs by role.

MemberDeclarationDescription
originalpublic var original: FfiBlobRef?The original ciphertext blob, when this entry carries one.
derivativespublic var derivatives: [FfiBlobRef]Derivative / metadata / provenance blobs.
public struct FfiBlobRef: Equatable, Hashable

FFI mirror of BlobRef — a blob’s content address and role.

MemberDeclarationDescription
ciphertextHashpublic var ciphertextHash: StringCiphertext content address (lowercase hex).
rolepublic var role: Stringoriginal | metadata | derivative | provenance.
sizepublic var size: UInt64Ciphertext size in bytes.
public struct FfiClassDeadline: Equatable, Hashable

One class and the instant its local notification should be armed for.

A class absent from pre_arm_deadlines’s result has no timer to hold: cancel whatever it has.

MemberDeclarationDescription
classpublic var `class`: FfiAlertClassThe class whose timer this is — also the notification.* catalog key the app renders when it fires.
deadlinepublic var deadline: StringWhen to fire it (RFC 3339).
public struct FfiClientBuild: Equatable, Hashable

A foreign client’s self-reported build identity: its product id and own semver. The git commit + dirty flag come from the core this app links, together composing the client_id/semver+commit client_version every manifest carries — so a defect in a shipped app build is traceable across the assets it produced.

MemberDeclarationDescription
clientIdpublic var clientId: StringThe app’s product id, e.g. capsule-ios.
semverpublic var semver: StringThe app’s own semantic version, e.g. 1.4.2.
public struct FfiEmbeddedPreview: Equatable, Hashable

A RAW original’s embedded JPEG, as embedded_preview reads it.

MemberDeclarationDescription
jpegpublic var jpeg: DataThe JPEG exactly as the RAW file carries it.
widthpublic var width: UInt32Upright width in pixels, once orientation is applied.
heightpublic var height: UInt32Upright height in pixels.
orientationpublic var orientation: UInt16The EXIF orientation (1..=8) to apply to jpeg’s stored pixels, in place of any tag the JPEG itself carries.
public struct FfiEscrowStored: Equatable, Hashable

FFI mirror of EscrowStored — what FfiSession::escrow_put did.

MemberDeclarationDescription
storedAtpublic var storedAt: StringWhen the server accepted the blob, RFC 3339, on the server’s clock.
replacedpublic var replaced: BoolWhether this displaced an earlier escrow: false is a first enrollment (recovery set up), true a replacement (the previous recovery secret has stopped working).
public struct FfiFeedDrain: Equatable, Hashable

A sync that drained the feed to its head, the evidence FfiWorkspace::expired_trash needs before it judges a retention deadline.

MemberDeclarationDescription
latestChangedAtpublic var latestChangedAt: String?The latest server changed_at the drain saw (RFC 3339), or None for a drain that saw no entry. Deadlines are judged at it alone, never by this device’s clock, so with None nothing has expired.
serverHeadspublic var serverHeads: [String: Data]Every asset the server’s feed lists, keyed by its id as the feed names it, with the manifest_cbor of its latest entry (empty for an entry that carries no record). An asset whose latest delete is not that head or before it is a held delete, which never expires. Ids that are not UUIDs name no asset of this workspace and are ignored. The map must cover the whole feed: a drain from the feed’s start (no cursor), or the heads this app kept, entry by entry, across every cursored drain since one. A drain resumed from a cursor lists only the assets that changed after it, so passing it alone leaves out every asset whose delete an earlier sync indexed: each is judged held and never expires, and the trash grows until the user empties it. That loses nothing, but the sweep stops working.
public struct FfiFeedEntry: Equatable, Hashable

FFI mirror of FeedEntry — one decoded sync feed entry. Ids/manifest/metadata cross as opaque bytes.

MemberDeclarationDescription
albumIdpublic var albumId: DataThe album this entry belongs to.
syncSeqpublic var syncSeq: UInt64Per-album, strictly-increasing anti-rewind high-water mark.
protocolVersionpublic var protocolVersion: StringThe album protocol pin (YYYY-MM-DD).
kindpublic var kind: FfiChangeKindWhat changed.
assetIdpublic var assetId: DataThe asset id.
manifestCborpublic var manifestCbor: DataThe signed AssetManifest as opaque canonical CBOR.
metadataBlobpublic var metadataBlob: DataThe encrypted metadata blob’s content address, as UTF-8 bytes. On a delete in trash, the asset’s live metadata blob, which a restore needs; empty for a purged delete.
blobspublic var blobs: FfiBlobManifestContent addresses of the asset’s blobs by role. On a delete in trash, only its original; none for a purged delete.
originalHeldpublic var originalHeld: BoolWhether the original blob is finalized server-side.
changedAtpublic var changedAt: StringWhen the change happened, RFC 3339, on the server’s clock.
public struct FfiHistorySources: Equatable, Hashable

FFI mirror of HistorySources: the blobs adopting an asset from its fetched history needs beyond its records.

MemberDeclarationDescription
metadataBlobHashpublic var metadataBlobHash: StringThe asset’s live metadata blob’s content address (lowercase hex), for [FfiSession::fetch_metadata_blob]; its bytes are the [FfiSyncHead::metadata_blob] [FfiWorkspace::open_synced_history] takes.
originalpublic var original: FfiBlobRefThe original the head commits to, role original, sized for [FfiSession::fetch_blob_to_path]; that file is the ciphertext_path [FfiWorkspace::open_synced_history] takes.
public struct FfiLocalHoldings: Equatable, Hashable

What this device holds for the asset. Flat, like FfiNotifyInput: an embedded preview is present when both of its dimensions are.

MemberDeclarationDescription
heldpublic var held: [FfiRepresentation]The encoded tiers and the original held locally; the LQIP is implied.
embeddedPreviewWidthpublic var embeddedPreviewWidth: UInt32?The embedded preview’s upright width, from [embedded_preview].
embeddedPreviewHeightpublic var embeddedPreviewHeight: UInt32?The embedded preview’s upright height.
public struct FfiManifestEnvelope: Equatable, Hashable

FFI mirror of ManifestEnvelope — the server-visible envelope fields the import pipeline builds and signs; opaque to this layer, carried verbatim.

MemberDeclarationDescription
cryptoSuiteIdpublic var cryptoSuiteId: UInt16Crypto suite the blob is sealed under.
protocolVersionpublic var protocolVersion: StringProtocol date (YYYY-MM-DD).
albumIdpublic var albumId: String?Optional album this blob belongs to.
fileIdpublic var fileId: StringThe asset id this blob belongs to (UUIDv7).
amkVersionpublic var amkVersion: UInt32AMK epoch the blob is sealed under.
ciphertextHashpublic var ciphertextHash: StringCiphertext content hash, lowercase hex.
plaintextSizepublic var plaintextSize: UInt64Plaintext size in bytes.
chunkSizepublic var chunkSize: UInt32STREAM plaintext chunk size.
keyModepublic var keyMode: Stringderived | wrapped.
metadataBlobHashpublic var metadataBlobHash: String?Hash of the bundle’s metadata blob, when known.
derivativeRolepublic var derivativeRole: String?A derivative blob’s role (thumbnail | preview | embedding); None for every other blob.
derivativeFormatpublic var derivativeFormat: String?A derivative blob’s format, e.g. image/jxl; set exactly when derivative_role is.
createdByUserpublic var createdByUser: StringThe authoring user.
createdByDevicepublic var createdByDevice: StringThe authoring device.
clientVersionpublic var clientVersion: StringThe exact client build.
timestamppublic var timestamp: StringRFC 3339 authoring timestamp.
actionpublic var action: StringLifecycle action (create for a fresh bundle).
priorProvenanceHashpublic var priorProvenanceHash: String?Prior provenance hash, for append-only chains.
retentionUntilpublic var retentionUntil: String?Optional retention floor.
public struct FfiNotifyInput: Equatable, Hashable

The device-held state the predicate decides from, flattened for the bindings.

Counts and instants only — no album id, no title, no asset id. An all-default value (every Option None, every count 0, an empty map) is the “just installed, learned nothing” input, and yields no alerts and no deadline.

MemberDeclarationDescription
lastCompletedSyncpublic var lastCompletedSync: String?When the last completed sync finished (RFC 3339). None on a device that has never completed one — which raises no sync_stale, because the alert is about a stale sync and not a missing one. When None, unsynced_changes is ignored.
unsyncedChangespublic var unsyncedChanges: UInt64Changes still waiting to reach the server, including originals still pending under a staged upload policy.
recoveryNextDuepublic var recoveryNextDue: String?When the next recovery-verification prompt becomes due (RFC 3339). Project it from the scheduler with RecoveryCadence::notify_facts rather than computing it here. None before recovery is set up, which ignores the other three recovery_* fields.
recoverySnoozedUntilpublic var recoverySnoozedUntil: String?When an active snooze on the recovery prompt expires (RFC 3339), if one is active. A snooze ending before recovery_next_due does not make the check due earlier. This is the canonical place to snooze the recovery check — not suppressed_until, which is for the other five classes.
recoverySnoozeBudgetSpentpublic var recoverySnoozeBudgetSpent: BoolWhether the consecutive-snooze budget is spent — the class has degraded to a persistent, non-blocking badge: still reported, no longer pre-armed.
recoveryRewrapDuepublic var recoveryRewrapDue: BoolWhether the scheduler has escalated to the guided re-wrap (repeated failures, or the user declaring the secret lost). Surfaces as the alert’s recovery parameter, so the app routes into the re-wrap flow instead of rendering a routine verification prompt.
quotaStatepublic var quotaState: FfiQuotaAdvisory?The state from the last GET /v1/quota. None before the first one.
quarantinePendingpublic var quarantinePending: UInt64How many items sit on the client’s quarantine surfaces awaiting a human. Excludes pending drops — they have their own class, so counting them here raises both.
dropsPendingpublic var dropsPending: UInt64How many guest drops are awaiting review and adoption.
suppressedUntilpublic var suppressedUntil: [String: String]Per-class snooze: class wire name (sync_stale, …) to the RFC 3339 instant the snooze runs until, exclusive. A class snoozed past now reports nothing, and its alarm is deferred to the snooze end rather than cancelled — a class snoozed after it fired must fire again when the snooze expires. Use disabled to turn a class off; do not encode that as a far-future instant here. An unrecognized class name is an [FfiError::InvalidArgument]. This map is for the other five classes. Recovery snoozing belongs in recovery_snoozed_until, which the cadence scheduler owns together with the budget that bounds it. An entry here for recovery_check_due is honoured as a fallback (the later of the two wins) but is not the canonical channel.
disabledpublic var disabled: [String]Per-class disable: the wire names of the classes the user turned off. They report nothing and hold no alarm, at any instant. Disabling suppresses the warning and never the behavior. An unrecognized class name is an [FfiError::InvalidArgument].
public struct FfiPathSnapshot: Equatable, Hashable

One sample of the path as the app’s OS listeners report it.

Flat, like FfiNotifyInput: background_deadline_ms present means the app runs in a background task the OS ends that many milliseconds from now; absent means the foreground.

MemberDeclarationDescription
costpublic var cost: FfiPathCostWhat the path costs.
reachablepublic var reachable: BoolWhether the OS reports a usable path.
interfacepublic var interface: FfiPathInterfaceThe interface the path runs over.
powerpublic var power: FfiPowerStateThe device’s power source.
thermalpublic var thermal: FfiThermalStateThe device’s thermal state.
backgroundDeadlineMspublic var backgroundDeadlineMs: UInt64?Milliseconds before the OS ends the background task the app runs in; None in the foreground.
public struct FfiPurgeOutcome: Equatable, Hashable

What a FfiWorkspace::purge_asset did.

MemberDeclarationDescription
assetIdpublic var assetId: StringThe purged asset (UUID string).
reissueRecordpublic var reissueRecord: Data?The signed shorter-retention delete an early purge appended, as the opaque canonical CBOR provenance record [FfiWorkspace::provenance_head] would have returned. It goes to the album’s ops surface so the server may purge too, through FfiSession::deliver_purge, which sends it with any delete it chains onto that the server never saw; when that delivery fails, [FfiWorkspace::purged_record] hands the same record back from the kept chain. None when the window had closed.
public struct FfiPushReport: Equatable, Hashable

What FfiSession::push_asset did — FFI mirror of AssetPushReport.

MemberDeclarationDescription
assetIdpublic var assetId: StringThe asset id (UUID string).
pushedpublic var pushed: [FfiPushedBlob]Every blob whose session was opened, in ladder order.
bytespublic var bytes: UInt64Total bytes handed to the upload client, the original included.
deferredpublic var deferred: UInt32Blobs the reported path held back ([FfiSession::set_path]): push the asset again once the path allows them. Zero when the whole ladder ran.
public struct FfiPushedBlob: Equatable, Hashable

How one blob of a pushed asset resolved — FFI mirror of PushedBlob.

MemberDeclarationDescription
tierpublic var tier: StringThe ladder tier the blob occupies (index / preview / original), the vocabulary [FfiUploadBlob::tier] uses.
hashpublic var hash: StringThe blob’s content address (lowercase hex).
outcomepublic var outcome: FfiUploadOutcomeWhether it transferred or merged onto a blob the server already held.
public struct FfiRegion: Equatable, Hashable

One live region of an asset and its labels.

MemberDeclarationDescription
regionIdpublic var regionId: StringThe region id (UUIDv7, minted fresh per create, never reused).
shapepublic var shape: FfiRegionShapeThe outline.
labelspublic var labels: [String]The region’s labels, sorted.
public struct FfiRenderRequest: Equatable, Hashable

A surface’s request: its intent and its size in device pixels when the view opened.

MemberDeclarationDescription
intentpublic var intent: FfiRepresentationThe rung the surface’s intent needs: Thumbnail for the grid, Preview for the single-asset view, Original for a full-fidelity view.
surfaceWidthpublic var surfaceWidth: UInt32The surface’s width when the view opened. A view that grows keeps passing this.
surfaceHeightpublic var surfaceHeight: UInt32The surface’s height when the view opened.
public struct FfiRenderStep: Equatable, Hashable

The answer to one render_step.

MemberDeclarationDescription
showpublic var show: FfiRenditionWhat to show now.
changedpublic var changed: BoolWhether show differs from the shown passed in.
nextpublic var next: FfiNextStep?What to obtain next, or None when the intent is met or nothing can raise it.
public struct FfiRendition: Equatable, Hashable

What a surface shows: a rung and its source. The caller keeps the last one and passes it back as shown.

MemberDeclarationDescription
rungpublic var rung: FfiRepresentationThe rung shown.
sourcepublic var source: FfiRungSourceWhat fills it.
public struct FfiStackMember: Equatable, Hashable

Where one live asset sits in its stack, read from its signed stack_membership register.

MemberDeclarationDescription
assetIdpublic var assetId: StringThe asset (UUID string).
stackIdpublic var stackId: StringThe stack it belongs to (UUID string).
isCoverpublic var isCover: BoolWhether it is the stack’s cover: its primary member, the “best photo”.
memberIndexpublic var memberIndex: UInt32?Its position within the stack (burst sequence, chapter index), when one was recorded.
public struct FfiStampedTransferEvent: Equatable, Hashable

A transfer event and when it arrived: milliseconds since the subscription opened, on a monotonic clock.

MemberDeclarationDescription
atMspublic var atMs: UInt64Milliseconds since the subscription opened.
eventpublic var event: FfiTransferEventThe event.
public struct FfiSyncEntry: Equatable, Hashable

One remote feed entry to apply, as a sync feed delivers it.

album_id and the two id-bearing fields carry the feed’s own 16-byte UUID form (the bytes album_id of capsule.sync.v1.SyncEntry), so an entry from FfiSession::sync_pull is passed through unchanged.

MemberDeclarationDescription
albumIdpublic var albumId: DataThe album the feed claims this entry belongs to — 16 raw UUID bytes. Cross-checked against the manifest by the chokepoint itself, never trusted on its own.
manifestCborpublic var manifestCbor: DataThe signed AssetManifest as opaque canonical CBOR, carried verbatim.
metadataBlobpublic var metadataBlob: DataThe sealed metadata blob the manifest commits to.
originalCiphertextpublic var originalCiphertext: DataThe asset’s original ciphertext. Required: the chokepoint’s content-integrity step hashes it against the manifest’s declared content address, and there is no variant of that check which skips it — so fetch the original blob before applying. It crosses whole, so this record costs the original’s full size in memory. An original of any size is received with FfiSession::fetch_blob_to_path, and applied and opened from that file with an [FfiSyncHead] through [FfiWorkspace::apply_sync_entry_from_file] and [FfiWorkspace::open_synced_original], in bounded memory.
localChainHeadpublic var localChainHead: Data?The 32-byte provenance head your catalog already holds for this asset, or None if it has never seen it. This is what decides replay and fork; feed back [FfiAssetFacts::provenance_head] after a successful apply.
public struct FfiSyncHead: Equatable, Hashable

One remote feed entry whose original is on disk rather than in hand: an FfiSyncEntry without its original_ciphertext, which FfiWorkspace::apply_sync_entry_from_file and FfiWorkspace::open_synced_original read from the file FfiSession::fetch_blob_to_path produced, in memory bounded by one STREAM chunk whatever the original’s size.

MemberDeclarationDescription
albumIdpublic var albumId: DataAs [FfiSyncEntry::album_id].
manifestCborpublic var manifestCbor: DataAs [FfiSyncEntry::manifest_cbor].
metadataBlobpublic var metadataBlob: DataAs [FfiSyncEntry::metadata_blob].
localChainHeadpublic var localChainHead: Data?As [FfiSyncEntry::local_chain_head].
public struct FfiSyncPage: Equatable, Hashable

FFI mirror of SyncPage — a decoded page plus the opaque cursor for the next pull (the client persists next_cursor and passes it back to advance).

MemberDeclarationDescription
entriespublic var entries: [FfiFeedEntry]The changes in this page, in strictly-increasing per-album sync_seq order.
nextCursorpublic var nextCursor: DataThe opaque cursor to pass to the next [FfiSession::sync_pull].
hasMorepublic var hasMore: BoolWhether the server holds changes beyond this page.
public struct FfiTrashEntry: Equatable, Hashable

One asset in the trash, as its latest signed delete record describes it.

MemberDeclarationDescription
assetIdpublic var assetId: StringThe trashed asset (UUID string).
deletedAtpublic var deletedAt: StringWhen the delete was signed (RFC 3339, UTC).
retentionUntilpublic var retentionUntil: String?The signed retention floor (RFC 3339, UTC): a restore is refused at or after it. None for a delete that carries no readable window, which never expires.
restorablepublic var restorable: BoolWhether a restore is accepted now, by this device’s clock. Computed by the workspace so no app re-implements the window; the server judges the same floor by its own clock.
public struct FfiUploadBlob: Equatable, Hashable

One transferable blob of a sealed asset, paired with the POST /upload body that declares it. Hand request and bytes straight to FfiSession::upload — this record exists so an app never has to assemble a manifest envelope itself.

The blobs come back in ladder order: the metadata blob (T0 — the index tier that makes the asset visible), then derivatives (T1), then the original (T2).

bytes holds each blob whole, the original included, so an original costs its full size in memory here. FfiSession::push_asset pushes the same blobs with the original streamed, and is the path for an original of any size.

MemberDeclarationDescription
tierpublic var tier: StringThe ladder tier this blob occupies (index / preview / original).
hashpublic var hash: StringThe blob’s content address (lowercase hex) — the value the server dedups on.
requestpublic var request: FfiUploadRequestThe POST /upload body for this blob, with the manifest envelope already projected.
bytespublic var bytes: DataThe bytes to transfer.
public struct FfiUploadRequest: Equatable, Hashable

FFI mirror of CreateUploadRequest — the POST /upload body.

MemberDeclarationDescription
sizepublic var size: UInt64Ciphertext size in bytes.
hashpublic var hash: StringCiphertext content hash, lowercase hex.
contentTypepublic var contentType: StringMIME type (closed enum per protocol version).
cryptoSuiteIdpublic var cryptoSuiteId: UInt16Crypto suite the blob is sealed under.
protocolVersionpublic var protocolVersion: StringProtocol date (YYYY-MM-DD) this client speaks.
blobRolepublic var blobRole: FfiBlobRoleThis blob’s role in its bundle.
manifestEnvelopepublic var manifestEnvelope: FfiManifestEnvelopeThe unencrypted manifest fields the server validates.
albumIdpublic var albumId: String?Optional album to add the asset to.
ownerIdpublic var ownerId: String?Optional owner id (defaults to the authenticated uploader).
intentIdpublic var intentId: String?Album-upgrade intent id (album upgrade ceremony only).
public struct FfiUploadStatus: Equatable, Hashable

FFI mirror of HeadInfo — the HEAD /upload/{id} resumption/status primitive.

MemberDeclarationDescription
offsetpublic var offset: UInt64Next expected byte (authoritative received-byte count).
totalSizepublic var totalSize: UInt64?Declared total size, when known.
statuspublic var status: StringSession state (X-Capsule-Upload-Status).
public enum FfiAlertClass: Equatable, Hashable

Every alert class, flattened for the bindings. A closed enum on both sides of the boundary.

MemberDeclarationDescription
syncStalecase syncStaleTwo weeks without a completed sync while changes remain un-synced.
recoveryCheckDuecase recoveryCheckDueA recovery-secret verification check is due.
quotaSoftcase quotaSoftStorage crossed the soft limit and is below the hard limit.
quotaGraceExpiringcase quotaGraceExpiringStorage is over the hard limit — the grace window is counting, or has closed.
quarantinePendingcase quarantinePendingItems are sitting on a quarantine surface awaiting a human.
dropPendingcase dropPendingGuest drops are awaiting review and adoption.
public enum FfiAlertSeverity: Equatable, Hashable

How prominently a client presents an alert. Neither variant gates anything — no alert blocks sync, unlock, upload, or any critical flow.

MemberDeclarationDescription
advisorycase advisoryInformational: nothing is failing, and nothing is about to.
warningcase warningSomething is already degraded or is being refused.
public enum FfiBlobRole: Equatable, Hashable

FFI mirror of BlobRole (snake_case on the wire, closed enum).

MemberDeclarationDescription
originalcase originalThe source asset.
derivativecase derivativeA client-generated thumbnail/preview.
metadatacase metadataThe CBOR metadata document.
provenancecase provenanceAppend-only provenance.
backupcase backupA library backup artifact.
derivativeManifestcase derivativeManifestA derivative tier’s signed manifest.
public enum FfiChangeKind: Equatable, Hashable

FFI mirror of ChangeKind.

MemberDeclarationDescription
createdcase createdA new asset became visible in the album.
updatedcase updatedAn existing asset advanced — new metadata, new bytes, or a restore.
deletedcase deletedThe asset was tombstoned.
public enum FfiCullFlag: Equatable, Hashable

The trinary culling flag, mirrored from core’s CullFlag. Neutral is the never-flagged state and what an unwritten register reads as.

MemberDeclarationDescription
pickcase pickA keeper.
neutralcase neutralNot yet culled either way.
rejectcase rejectMarked for rejection: a candidate for the reject sweep.
public enum FfiDeviceTier: Equatable, Hashable

Device hardware tier, selecting the Argon2id cost at wrap time. Mirrors core’s DeviceTier in this namespace (see the module docs on why it is not shared).

MemberDeclarationDescription
lowRamcase lowRam≤ 2 GiB total RAM (entry-level Android / embedded).
normalcase normalDefault for phones and laptops.
desktopcase desktop≥ 8 GiB; used when wrapping new escrow blobs from a desktop.
public enum FfiError: Swift.Error, Equatable, Hashable, Foundation.LocalizedError

Every failure the FFI flows can surface, flattened by originating layer. Each variant carries the stable error.* catalog code (when one applies — clients localize it) and the English detail (stays English), mirroring the SDK’s { error, code } contract so foreign apps switch on the code, never a bare HTTP status.

MemberDeclarationDescription
Authcase AuthAn authentication flow (login/register/refresh/logout) failed.
Uploadcase UploadAn upload flow (create/transfer/status) failed.
Synccase SyncA sync flow (pull/reconcile) failed.
Workspacecase WorkspaceA local workspace operation failed — opening the library, sealing an asset, reading a managed asset. This is the workspace failing, never an entry being refused: a refused sync entry is a [FfiSyncApplyOutcome::Quarantined] verdict, not an error.
Labelcase LabelA label or region edit was refused before anything was written: its target does not exist, the label rule refused the label, or the shape broke the normalized-coordinate contract. refusal says which, so an editor shows the reason rather than parsing detail.
Trashcase TrashA trash verb (delete, restore, purge) was refused before anything was written: the asset is unknown, not in the trash, or past its signed retention window. refusal says which.
Escrowcase EscrowA master-key escrow flow (store/fetch) failed.
Directorycase DirectoryA device-directory flow (publish/fetch) failed.
Fetchcase FetchA blob fetch ([FfiSession::fetch_blob_to_path]) failed: the blob is gone, still awaiting its original, refused, did not hash to its content address, or could not be written locally. Nothing is left at the destination path.
InvalidArgumentcase InvalidArgumentA foreign argument was structurally invalid (e.g. an un-parseable URL) before any network call.
public enum FfiHardwareSignerError: Swift.Error, Equatable, Hashable, Foundation.LocalizedError

Failure surfaced by a foreign FfiHardwareSigner. Mirrors capsule-core’s HardwareSignerError variant for variant — it is the same contract, declared in the capsule_sdk namespace because core’s own type carries no uniffi derive.

The error is thrown by foreign code — it is the declared error of the FfiHardwareSigner and FfiHardwareKeyAgreement foreign traits — so Rust must lift it, and uniffi lifts only a non-flat error. Every variant therefore carries a named detail field (the element’s own English diagnostic), which also gives the Kotlin exceptions and Swift cases the same (detail) shape. Core’s error keeps detail only on Backend; for the other variants it is logged at the seam and dropped.

MemberDeclarationDescription
AuthCancelledcase AuthCancelledThe user cancelled the biometric / the element refused authentication.
Unavailablecase UnavailableNo secure element is available on this device.
NotFoundcase NotFoundNo key exists for the requested alias.
Exportablecase ExportableThe private key was readable — non-exportability is violated (a failure).
Backendcase BackendAny other backend error.
public enum FfiLabelRefusal: Equatable, Hashable

Why a label or region edit was refused (FfiError::Label). Nothing was written.

MemberDeclarationDescription
notFoundcase notFoundThe asset, region, stack or album does not exist, or no longer does.
labelEmptycase labelEmptyThe label is empty once trimmed.
labelTooLongcase labelTooLongThe normalized label is this many UTF-8 bytes, past the 256-byte bound.
regionOutOfRangecase regionOutOfRangeA coordinate is not a finite number in [0, 1].
regionEmptycase regionEmptyA radius, width or height is zero.
regionPastEdgecase regionPastEdgeThe shape extends past an edge of the image.
public enum FfiLabelTarget: Equatable, Hashable

What a label attaches to beyond the asset itself, whose own labels are its user tags. The label rule is the same at every level.

MemberDeclarationDescription
regioncase regionA region of an asset.
stackcase stackA stack (UUID string).
albumcase albumA container album (UUID string).
public enum FfiLifecycleOpOutcome: Equatable, Hashable

What became of one lifecycle op.

MemberDeclarationDescription
appliedcase appliedThe server applied the op, or had applied it already (replayed).
notIndexedcase notIndexedNothing was sent: the feed does not list the asset, so its create is not indexed on the server, and the server never sees a delete for an asset it has not indexed. The record stays on the asset’s chain; deliver it once a sync lists the asset.
restoreTooLatecase restoreTooLateNothing was sent: a trash-restore in the stretch undoes a delete the server has not seen, and that delete’s window closed at retention_until. Sent, the server would apply the delete, refuse the restore and purge an asset the user restored in time. Withheld for good: the server keeps the asset live at the head it holds, the workspace keeps every record, and nothing after the pair can be sent either, since the chain is hash-linked. A later delivery judges it the same way, so it never needs retrying.
failedcase failedThe server refused the op, or could not be reached. permanent says whether sending the same bytes again can only be refused again.
public enum FfiLoginOutcome

What a password login answered with, across the FFI.

An enum rather than an optional session handle: “signed in” and “needs a code” are different states an app renders differently, and a null handle beside a mfaToken string would let a caller read one while acting on the other.

MemberDeclarationDescription
sessioncase sessionThe account has no second factor, and this is its session.
secondFactorRequiredcase secondFactorRequiredThe password verified and a code is still needed.
public enum FfiNextStep: Equatable, Hashable

How to obtain the next rung.

MemberDeclarationDescription
fetchcase fetchFetch it from the server.
generatecase generateProduce it on the device from the original it holds.
public enum FfiOpenedOriginal: Equatable, Hashable

How FfiWorkspace::open_synced_original resolved. Only Opened leaves anything at the destination path.

MemberDeclarationDescription
openedcase openedVerified end to end and decrypted: the destination now holds the original’s plaintext, which hashes to the signed sidecar’s digest.
refusedcase refusedThe entry did not apply, exactly as apply_sync_entry would say: held pending or quarantined. A ciphertext file that is not the original the manifest commits to is quarantined as Rejected.CiphertextHashMismatch.
undecryptablecase undecryptableThe entry applied, but its file key could not be recovered or its ciphertext failed authentication under it.
digestMismatchcase digestMismatchThe ciphertext opened, but the plaintext does not hash to the signed sidecar’s digest.
public enum FfiPathCost: Equatable, Hashable

FFI mirror of PathCost.

MemberDeclarationDescription
unmeteredcase unmeteredBulk transfer is acceptable.
meteredcase meteredThe link is byte-counted (iOS isExpensive, Android without NOT_METERED).
constrainedcase constrainedThe OS data saver is on (iOS Low Data Mode, Android Data Saver).
public enum FfiPathInterface: Equatable, Hashable

FFI mirror of PathInterface.

MemberDeclarationDescription
wiredcase wiredEthernet or another wired link.
wificase wifiWi-Fi.
cellularcase cellularA cellular radio.
othercase otherAnything else, or unknown.
public enum FfiPowerState: Equatable, Hashable

FFI mirror of PowerState.

MemberDeclarationDescription
externalcase externalOn external power.
batterycase batteryOn battery.
lowPowercase lowPowerOn battery with the OS low-power mode on.
public enum FfiPushAssetResult: Equatable, Hashable

How one asset of an FfiSession::push_assets call ended.

MemberDeclarationDescription
pushedcase pushedThe asset’s admitted ladder ran: what [FfiSession::push_asset] would have returned.
failedcase failedThe asset did not push: the error [FfiSession::push_asset] would have failed with. It stays local, and pushing it again is safe.
public enum FfiQuotaAdvisory: Equatable, Hashable

The quota state, as GET /v1/quota reports it.

MemberDeclarationDescription
okcase okBelow the soft limit. All uploads succeed normally.
softWarningcase softWarningAt or above the soft limit and below the hard limit. Uploads succeed; the client warns.
hardExceededcase hardExceededAt or above the hard limit. New uploads are rejected; the grace window is counting.
graceExpiredcase graceExpiredOver the hard limit for longer than the grace window; metadata-growth writes are refused too.
suspendedcase suspendedAn admin or billing action rather than a threshold. Raises no quota alert.
public enum FfiRegionShape: Equatable, Hashable

A region’s outline, in coordinates normalized to [0, 1] over the original’s display orientation. Mirrors capsule_core::sidecar::RegionShape, single precision included, so a shape that validates on one side validates on the other.

MemberDeclarationDescription
circlecase circleA circle centred at (cx, cy) (fractions of width and height) with radius r, a fraction of the image’s shorter side.
rectcase rectA rectangle with its top-left corner at (x, y).
public enum FfiRepresentation: Equatable, Hashable

A rung of the ladder, cheapest first.

MemberDeclarationDescription
lqipcase lqipThe LQIP, embedded in the metadata blob; always present.
thumbnailcase thumbnailThe grid thumbnail.
previewcase previewThe single-asset view’s preview.
originalcase originalThe original.
public enum FfiRetryReason: Equatable, Hashable

Why an upload chunk is sent again. Mirrors RetryReason variant for variant.

MemberDeclarationDescription
transportResetcase transportResetThe connection reset or the request was lost; the same chunk is re-sent after the wait.
offsetRealigncase offsetRealignThe server named a different offset; the transfer continues from it.
sessionLostcase sessionLostThe session was gone; a new one is created and the transfer continues from the offset it reports.
serverWaitcase serverWaitThe server asked the client to wait; afterwards the transfer resumes from the offset HEAD reports.
public enum FfiRungSource: Equatable, Hashable

Where a shown rung’s pixels come from.

MemberDeclarationDescription
lqipcase lqipThe LQIP (render_lqip), which paints the dominant colour where it does not decode.
embeddedcase embeddedThe original’s embedded preview ([embedded_preview]).
encodedcase encodedThe rung’s encoded tier.
originalcase originalThe original, decoded.
public enum FfiStackCullState: Equatable, Hashable

A stack’s derived cull state, by the doc’s precedence all-rejected → any-pick → else mixed.

MemberDeclarationDescription
allRejectedcase allRejectedEvery member is rejected: the whole stack is a sweep candidate.
anyPickcase anyPickAt least one member is picked.
mixedcase mixedNeither: undecided, or rejects mixed with neutrals.
public enum FfiSyncApplyOutcome: Equatable, Hashable

How applying one remote entry resolved — the three verdicts the client validation duties permit. There is no “ignored” case: a failure is always a named quarantine or a hold.

MemberDeclarationDescription
appliedcase appliedVerified end to end; upsert the facts.
pendingcase pendingAuthorization is intact but the epoch’s AMK has not arrived locally yet. Hold and retry — never quarantine.
quarantinedcase quarantinedRefused. reason is a stable, switchable code; detail is English developer text.
public enum FfiThermalState: Equatable, Hashable

FFI mirror of ThermalState.

MemberDeclarationDescription
nominalcase nominalNo thermal pressure.
faircase fairSlightly elevated.
seriouscase seriousHigh: the OS asks apps to reduce work.
criticalcase criticalCritical: the OS asks apps to stop discretionary work.
public enum FfiTransferEvent: Equatable, Hashable

One step of an upload, a custody check or a ranged fetch. Mirrors TransferEvent variant for variant and field for field; blob is the content address (lowercase hex) and offsets and lengths are bytes of ciphertext.

MemberDeclarationDescription
sessionCreatedcase sessionCreatedPOST /upload opened a session; resume_offset is non-zero when the server returned an existing session for the same blob.
sessionResumedcase sessionResumedAn existing session resumed from the offset HEAD reported.
chunkSentcase chunkSentA chunk left the client.
chunkAckedcase chunkAckedThe server acknowledged a chunk; acked_offset is its new received-byte count.
chunkRetriedcase chunkRetriedA chunk will be sent again, or the transfer moves to another offset or session.
checksumRejectedcase checksumRejectedThe server refused a chunk whose body did not match its checksum; it is re-sent.
finalizecase finalizeEvery declared byte reached the server; it is finalizing the blob.
mergedcase mergedThe server already held the blob: resolved as a merge, nothing sent.
verifycase verifyPOST /storage/verify answered for an asset.
receiptcase receiptA custody receipt for one blob verified under the pinned attestation keys.
rangeRequestedcase rangeRequestedA ranged request left the client; max_len is the bounded window, or None for the open-ended remainder.
rangeReceivedcase rangeReceivedBytes of a ranged request landed.
fetchRetriedcase fetchRetriedA ranged request brought nothing; the fetch backs off and resumes from the same offset.
fetchVerifiedcase fetchVerifiedThe fetched bytes hashed to the requested content address.
integrityRejectedcase integrityRejectedThe fetched bytes did not hash to the requested content address and were discarded.
public enum FfiTransferFeedItem: Equatable, Hashable

What one pull of a subscription yields.

MemberDeclarationDescription
eventcase eventThe next event.
laggedcase laggedThe subscriber fell behind and this many events never arrived. The transfers carried on; only their progress went unseen.
public enum FfiTrashRefusal: Equatable, Hashable

Why a trash verb was refused (FfiError::Trash). Nothing was written.

MemberDeclarationDescription
notFoundcase notFoundThe workspace holds no such asset.
notInTrashcase notInTrashThe asset is not in the trash: a restore has nothing to undo, and a purge never skips the trash.
retentionElapsedcase retentionElapsedThe signed retention window has closed, so the asset can no longer be restored. It stays in the trash until it is purged.
public enum FfiUploadOutcome: Equatable, Hashable

FFI mirror of UploadOutcome — how a full upload resolved.

MemberDeclarationDescription
completedcase completedEvery declared byte transferred; the server has begun finalization.
alreadyStoredcase alreadyStoredThe blob was already stored server-side (the duplicate_blob → merge path).
public enum FfiVerifyOutcome: Equatable, Hashable

The verification verdict, flattened for FFI (reasons as stable reason-code strings).

MemberDeclarationDescription
acceptcase acceptThe asset verified and may enter the trusted set.
terminalRejectcase terminalRejectThe asset is permanently rejected (quarantined); reason is the reason code.
pendingcase pendingThe asset is recoverable-pending (held + retried); reason is the reason code.