Safety
Sync policy
SyncPolicy enforces runtime exclusions. A guard is held until fetch or push
returns; a conflicting acquisition fails with AlreadyRunning.
| Procedures started concurrently | Same remote | Different remotes |
|---|---|---|
| Fetch + fetch | Denied | Denied |
| Fetch + push | Denied | Allowed |
| Push + push | Denied | Allowed |
| Confirmation + fetch or push | Denied | Allowed |
A standalone confirmation takes the same per-remote slot as fetch and push, so it cannot run while one of them is working on that remote. Push preparation takes no slot at all: it reads local files only and contacts no remote.
Existing resource restrictions
| Restriction | Protects | Effect |
|---|---|---|
| Local operation-log mutex | operations.log | Serializes one synchronous operation-log access at a time. The guard is released before an await. |
| Per-remote media-inventory mutex | remotes/{remote_id}/state/media/media_list.json | Serializes synchronous inventory read-modify-write work. It is never held across a network await, including the folder listings of a confirmation. |
In-memory operation-state RwLock | Reconstructed in-memory state | Multiple readers may coexist; rebuilding installs state under the exclusive write lock. |
| Remote compaction lock file | operations/LOCK.op | Only the client that created the lock with put_if_absent may compact. A client that cannot create it skips compaction. A stale lock requires manual removal. |
Access boundaries
To restrict remote-storage access, fetch receives only StorageRead, while push
receives StorageReadWrite.
Local-state subdirectory access is not strictly enforced. However, path builders are separated by domain, so each caller receives a focused handle for the part of local state it needs.
The safety assessments below apply to concurrent work within one Library
instance.
In-memory state
| Resource | Fetch implementation input | Push implementation input | Local-edit implementation input | Safe? |
|---|---|---|---|---|
| Reconstructed in-memory state | Operation-state RwLock (through Library) | Operation-state RwLock (through Library) | Operation-state RwLock (through Library) | Safe RwLock |
Remote storage
| Resource | Fetch implementation input | Push implementation input | Local-edit implementation input | Safe? |
|---|---|---|---|---|
| Remote storage | StorageRead | StorageReadWrite | Not used | Safe Only one sync operation per remote |
Local state
| Resource | Fetch implementation input | Push implementation input | Confirmation input | On-demand media download input | Local-edit implementation input | Safe? |
|---|---|---|---|---|---|---|
local_state/library/ | LocalStateLibraryDir | Not used | Not used | Not used | Not used | Safe Only one fetch per library |
remotes/{remote_id}/state/operations/ | RemoteLastKnownStateDir | RemoteLastKnownStateDir | Not used | Not used | Not used | Safe Only one sync operation per remote |
remotes/{remote_id}/state/compact_op_id_merged_to_local.json | RemoteCompactOpIdMergedToLocal | Not used | Not used | Not used | Not used | Safe Fetch-exclusive; corruption intentionally fails fetch |
remotes/{remote_id}/state/media/media_list.json | RemoteMediaList | RemoteMediaList | RemoteMediaList | Not used | Not used | Safe Per-remote inventory mutex plus atomic writes |
local_state/media/ | Not used | LocalStateMediaDir | Not used | LocalStateMediaDir (through Library) | LocalStateMediaDir (through Library) | Safe Atomic write; immutable file with a 1-to-1 filename/content relation |
operations.log | LocalOpsReadWriteLock | LocalOpsReadWriteLock (through Library) | Not used | Not used | LocalOpsReadWriteLock (through Library) | Safe Mutex |
Remote media inventory
The sibling path accessors enforce ownership within the shared state/ parent:
RemoteLastKnownStateDir owns only the remote's last-known operation state, RemoteCompactOpIdMergedToLocal owns only the
merge marker, and RemoteMediaList owns only the media inventory.
media_list.json is a positive-only inventory: an entry confirms a media's data blob, its
thumbnail, or both, independently of each other. A blob is confirmed by media availability
confirmation, which runs inside fetch, inside push, and on its own when a user asks for it, or
by push after a successful ensure-present upload. An unconfirmed blob means unconfirmed, not
absent from the remote.
The inventory is not authority over lifecycle. Planning first selects IDs from current resolved
CRDT state: trashed media remains eligible for confirmation and upload, while a hard-deleted ID is
not eligible even if an old inventory entry positively remembers it. After a durable
MediaDeletion is pushed, Push deletes that target remote's data and thumbnail objects and removes
the positive inventory entry. This ordering prevents physical reclamation from making a still-live
item unavailable.
Push preparation is the main reader. It resolves where each blob the target is missing can be read from, using nothing but these inventories and the local media cache, which is why an incomplete inventory shows up as an unresolved media rather than as a wrong upload.
Downloading a blob on demand to display it is not a writer. It proves only that the blob was readable at that moment, and the confirmation step of the next sync with that remote establishes the same fact, so the download path leaves the inventory alone.
Confirmation is optional bookkeeping, so an inventory write failure cannot prevent a validated
operation fetch. Both writers use a per-remote non-async mutex, held only for synchronous
local load-modify-save work and never across list, get, or put_if_absent awaits. Atomic
file replacement prevents partial JSON contents; the mutex prevents lost concurrent updates.
compact_op_id_merged_to_local.json is written only by fetch. The global fetch slot makes it fetch-exclusive
within a Library instance, and corruption of this merge marker intentionally fails fetch.