Skip to main content

Operations

Everything except media blob content, thumbnails, and encryption keys is stored as operations. They form a growing set of immutable CRDT-style units of modification on the library. For example: renaming a photo, creating an album, adding a member. Library state is reconstructed deterministically from the full list of operations.

Operation Group Envelope

Operations are grouped in an operation group. It also contains a unique id, the user who created it, and the parent operation group for ordering.

Operation Group
op_idOpUuid (UUIDv7)
parent_op_idOption<OpUuid>
authorLibraryUsername
operationsVec<Operation>

Contrary to all other UUIDs in the format, operation group IDs are UUIDv7, so when sorting concurrent groups they are tie-broken by date.


Ordering and Reconstruction

State is reconstructed by performing a topological sort of the op group DAG (following parent_op_id links), then replaying operations in that order.

When multiple groups share the same parent, the tie-break is ascending op_id. Because op_id is UUIDv7, this gives "earliest clock wins" semantics on concurrent writes. Every client independently computes the same order, making reconstruction deterministic.


Operation List

Each Operation carries its own timestamp (time of write). This is not used in the logic.

Media

Media Creation
Adds a new media item to the library.
media_idMediaUuid
filename_originalString
dateDateTime<Utc>Capture date (EXIF DateTimeOriginal / PHAsset.creationDate).
storage_dateStorageDateStorageDate { year: u16, month: u8 } determines the storage path. Usually matches date but can differ.
size_bytesu64
content_hash[u8; 32]BLAKE3 hash of the unencrypted file bytes, stored as raw bytes.
modified_atOption<DateTime<Utc>>Last modification date (PHAsset.modificationDate or file mtime). None if unknown.
gpsOption<GpsCoords>GPS position at capture time. GpsCoords { latitude: f64, longitude: f64 } in WGS84 decimal degrees.
apple_aae_media_idOption<MediaUuid>Points at another MediaCreation's media_id, the Apple .AAE edit sidecar for this media item. The referenced entry is never added to an album or group, so it never appears in listings on its own.
Media Rename
Sets or clears the user-facing display name.
media_idMediaUuid
nameOption<String>
Media Props Update
Attaches or updates a single key/value property.
media_idMediaUuid
keyString
valueString (JSON)

Albums

Album Creation
Creates a new album with an optional parent.
album_idAlbumUuid
nameString
album_id_parentOption<AlbumUuid>
Album Media Add
Adds a media item to an album.
album_idAlbumUuid
media_idMediaUuid
Album Media Remove
Removes a media item from an album.
album_idAlbumUuid
media_idMediaUuid
Album Deletion
Soft-deletes an album.
album_idAlbumUuid
Album Rename
Renames an album.
album_idAlbumUuid
nameString
Album Reparent
Moves an album to a new parent.
album_idAlbumUuid
new_parent_idOption<AlbumUuid>
Album Thumbnail Set
Sets or removes the thumbnail for an album.
album_idAlbumUuid
media_idOption<MediaUuid>

Groups

Groups are leaf collections. They hold media items directly, cannot have child albums, and each belongs to exactly one parent album set at creation.

Group Creation
Creates a new group inside an album. Parent is permanent.
group_idGroupUuid
album_id_parentAlbumUuid
Group Media Add
Adds a media item to a group. Idempotent.
group_idGroupUuid
media_idMediaUuid
Group Media Remove
Removes a media item from a group.
group_idGroupUuid
media_idMediaUuid
Group Deletion
Soft-deletes a group. No cascade ops are emitted.
group_idGroupUuid

File Format

Every operation file is serialized as CBOR and encrypted before being written to the remote. Each op file is immutable. Each file is named by a UUID with a tier-dependent extension:

  • A compaction file (.op{N}_{count}) holds many op groups. Its name is a fresh random UUID assigned to the file itself, N is the tier, and count is the total number of operations inside, summed across every op group in the file — readable from the filename without decrypting the file.

Compaction

Even if the operation group set as a whole is growing, and each group immutable, the way they are stored in remotes can sometimes change. We do this to bound the total number of operation files, and the number of files we have to touch to add new groups. To do this we use a Log-Structured Merge-trees (LSM) inspired algorithm.

Local vs Remote

Compaction never happens locally. The two sides store ops differently:

  • Local keeps a single append file, local_state/operations.log, that accumulates op groups as you make changes. It is never compacted. There is also a pending.op file that accumulates groups waiting to be pushed, until it is flushed or grows too large.
  • Remote stores ops as tiered files and compacts them in the background. The advantage of this is that all compacted states are independent for all remotes, so any change to the procedure can be implemented without problems.

Compaction File Format

Compaction is built on op group identity: every group has a unique op_id, and a compaction file is just the full op groups it covers, each tagged by its op_id.

Compaction file (.op{N}_{count})
A remote-only file packing op groups together, {count} being their total operation count. Immutable and self-contained.
tieru8
contentsVec<{ op_id: OpUuid, group: OperationGroup }>

Compaction Tiers

Files are organized into tiers. Tier 0 holds raw op groups; higher tiers hold compaction files that pack more and more groups together.

Two limits govern the files at each tier: how many operations a single file at that tier may hold (summed across every op group packed into it), and how many files may exist at that tier before compaction is triggered. Because each compaction file encodes its total operation count in its filename, clients can evaluate both limits from the directory listing alone.

TierFile extensionOps limitTier files limit
1.op1_{count}2010
2.op2_{count}20010
N.opN_{count}20×10^(N-1)10

With this scheme, the maximum number of operations storable with tier i or less:

Max tier1234567
Max total ops2002,20022,200222,2002,222,20022,222,200222,222,200
Max compacted op files10203040506070

Compaction Procedure

When any tier has more files than its file limit, the client responsible for this must compact it. The goal is to completely evict that tier. All its files are merged into a single file at the next tier up, leaving no underpopulated file behind. For now this is done greedily in arbitrary order.

  1. The client acquires a global operations lock file on the remote, preventing concurrent compactions.
  2. It reads all files at tier N and merges their op-group lists into a single new compaction file at tier N+1. "All files at tier N" means all tier-N files recorded in the pushing client's own last known state for that remote (see Sync), not a fresh listing of the remote. A client never lists or reads remote op files outside of what it already knows about to decide what to compact.
  3. The tier-N source files are deleted, still under the same lock.
  4. If tier N+1 now exceeds its file limit, the procedure is applied again at that tier, cascading upward until no tier is over its limit, all while continuing to hold the same lock acquired in step 1.
  5. Once no tier is over its limit, the lock is released.

Uploading Operations

When pushing, the client writes new operation groups directly as a single compaction file, at the lowest tier whose ops limit can hold the whole batch's total operation count. A push totaling 200 operations, for example, lands directly at tier 2 rather than as an oversized tier-1 file, regardless of how many op groups those operations are split across.


Local Log Format

operations.log is an append-only binary file that lists all the operation groups. It is a sequence of frames like this:

op_id 16 bytes op group UUID
blob_len 4 bytes unsigned 32-bit integer
blob blob_len CBOR-serialized OperationGroup, encrypted

Rolling out our own format is questionnable, but it's the simplest way I found to write all the operations in append mode without rewriting everything each time.