Per-file locks: store, storage guard, API, real WebDAV locks, UI (#315) #393

Merged
Cordy merged 13 commits from file-lock into main 2026-09-02 19:15:25 +00:00
Owner

Implements the per-file lock design ratified on #315 (L1-L10).

Backend

  • internal/storage/filelock: statestore-backed lock store (locks.json, tenth managed state file) with in-memory index, plus a Wrap guard decorator between trash and ownermeta. Write/Delete/Rename/Copy-onto are refused for non-holders with the new storage.ErrLocked; reads are never blocked. Locks follow holder renames, drop on delete and on trashing.
  • Lock types: user (manual, indefinite) and dav (token-bound, expiring, lazily reaped); app reserved for the co-editing epic.
  • API: POST/DELETE /api/v1/lock. Unlock rights: holder, admins, owning space owner; breaking another user's lock is audited as break-lock (no silent admin write-through - the files_lock CVE lesson). writeErr maps ErrLocked to 423.
  • Listings enrich locked/lockOwner (pure in-memory lookups).
  • WebDAV LOCK/UNLOCK are now real (previously fake grants): opaquelocktoken UUIDs, Timeout parsing (30s-1h, default 10m), refresh, 423 with no-conflicting-lock on conflicts, lock-token-submitted on locked writes, token-validated UNLOCK, activelock in PROPFIND. A holder's manual lock is not downgraded by a DAV LOCK.

UI

  • Row menu Lock/Unlock (files only), data-t="lock" chip "Locked" with holder tooltip, success/failure toasts, i18n x4.

Tests: store lifecycle/expiry/persistence, guard verb x identity matrix, rename-carries/trash-drops/delete-removes, full DAV flow (grant, conflict 423, locked PUT 423, holder PUT, lockdiscovery, UNLOCK token validation), manual-lock-blocks-DAV; mutation check on the holder comparison; web extract/check/tests.

Deferred (recorded on #315): admin-configurable expiry cap for user locks (needs a settings row); space-owner folder-locking instance setting; permission-aware hiding of Unlock in the menu (server enforces, client shows the action and surfaces 403).

Closes the third and final part of #315.

Implements the per-file lock design ratified on #315 (L1-L10). **Backend** - `internal/storage/filelock`: statestore-backed lock store (`locks.json`, tenth managed state file) with in-memory index, plus a `Wrap` guard decorator between trash and ownermeta. Write/Delete/Rename/Copy-onto are refused for non-holders with the new `storage.ErrLocked`; reads are never blocked. Locks follow holder renames, drop on delete and on trashing. - Lock types: `user` (manual, indefinite) and `dav` (token-bound, expiring, lazily reaped); `app` reserved for the co-editing epic. - API: `POST/DELETE /api/v1/lock`. Unlock rights: holder, admins, owning space owner; breaking another user's lock is audited as `break-lock` (no silent admin write-through - the files_lock CVE lesson). `writeErr` maps `ErrLocked` to 423. - Listings enrich `locked`/`lockOwner` (pure in-memory lookups). - WebDAV LOCK/UNLOCK are now real (previously fake grants): opaquelocktoken UUIDs, Timeout parsing (30s-1h, default 10m), refresh, 423 with `no-conflicting-lock` on conflicts, `lock-token-submitted` on locked writes, token-validated UNLOCK, activelock in PROPFIND. A holder's manual lock is not downgraded by a DAV LOCK. **UI** - Row menu Lock/Unlock (files only), `data-t="lock"` chip "Locked" with holder tooltip, success/failure toasts, i18n x4. **Tests**: store lifecycle/expiry/persistence, guard verb x identity matrix, rename-carries/trash-drops/delete-removes, full DAV flow (grant, conflict 423, locked PUT 423, holder PUT, lockdiscovery, UNLOCK token validation), manual-lock-blocks-DAV; mutation check on the holder comparison; web extract/check/tests. **Deferred (recorded on #315)**: admin-configurable expiry cap for user locks (needs a settings row); space-owner folder-locking instance setting; permission-aware hiding of Unlock in the menu (server enforces, client shows the action and surfaces 403). Closes the third and final part of #315.
Cordy merged commit 248e46e0e7 into main 2026-09-02 19:15:25 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Cordy/Cairn#393
No description provided.