Skip to main content

Versioning

Lasco has no single format version. Only the layers that cross a device boundary, or that cannot be rebuilt from something else, carry a version indicator. Everything that is device local and regenerable is left unversioned and evolves through its serde representation instead.

Each version that does exist is checked by strict equality against the constant compiled into the current build. There is no range of accepted versions and no migration path: a value that does not match the expected one is rejected with an error.

LayerConstantCurrent valueDefined in
Encrypted blobBLOB_FORMAT_VERSION1 (u8)encryption/blob.rs
Master key filePROTOCOL_VERSION1 (u32)library/mod.rs
Library directoryLIBRARY_FORMAT_VERSION1 (u32)library/mod.rs
S3 secret keyS3_SECRET_ENCRYPTION_DESCRIPTION"AES-256-GCM v1"s3_secret.rs

config.json, library.json, the CRDT snapshot, and the local operation log carry no version field at all. They are device local and are never uploaded to a remote, so no other device or build has to agree on their shape. The configuration files evolve through their serde representation, using #[serde(default)] for added fields and #[serde(alias)] for renames. The snapshot and the log are caches of state the operation history already holds, so a file that cannot be decoded is discarded and rebuilt rather than migrated.

New operation compatibility​

ApplePhotosCollectionLinkAdded changes the operation enum. A library containing this operation must not sync with a release that cannot deserialize it: unknown operation variants are never silently discarded. Releases that introduce or consume this operation therefore require an explicit rollout policy that prevents older clients from syncing affected libraries.


Encrypted blob format​

Every encrypted blob begins with a single byte version header, followed by the XChaCha20 nonce and the ciphertext:

[ format_version: u8 (1 byte) | nonce: 24 bytes | ciphertext ]

This envelope is shared by media files (.data), thumbnails (.thumb), remote operation files (.op), the CRDT snapshot, and each frame of the local operation log. Decoding a blob whose first byte is not BLOB_FORMAT_VERSION fails with an unknown blob format version error.

The version covers the envelope only. It says nothing about the shape of the plaintext inside, which is versioned separately where it is versioned at all.


Master key protocol version​

Each per-user master key file (mk_{username}_{uuid}.enc) stores a big endian u32 before the AES-GCM nonce and the ciphertext:

[ protocol_version: u32 (4 bytes, big endian) | nonce: 12 bytes | ciphertext ]

The constant is named PROTOCOL_VERSION and lives in library/mod.rs, but the master key file is the only place it is written or checked.


Library directory format​

The library format version is an empty sentinel file whose name encodes the version. The filename is derived from LIBRARY_FORMAT_VERSION by library_format_sentinel(), so the number and the name cannot drift apart:

local_state/library/
version_1

It is written on init, next to library_id_{uuid}, library_salt, and the master key files. Library::open requires the file named version_1 to be present in the local library directory and fails otherwise, so a directory written by a build with a different sentinel name is refused.

This is the one coarse gate for the on-disk layout. It is meant to be incremented only for a change an older build must not open at all, not for the ordinary addition of a field to a config file. Note that config.json is read before any library is opened, so it sits outside this gate.

initialize_remote copies the whole local library/ directory to a remote, so the sentinel also ends up under library/version_1 on the remote. Nothing reads it back from there: the check is performed against the local copy only.


Remote secret key version​

There is no separate per remote credential file. S3 credentials live in library.json, and the secret key's version is a plain string tag compared by equality:

{
"secret_key_encrypted": "...",
"secret_key_encryption_description": "AES-256-GCM v1"
}

Decryption is refused when the description does not match S3_SECRET_ENCRYPTION_DESCRIPTION.