Task 6: Remote interface and in-memory implementation #6
Labels
No labels
data-integrity
engine
platform
procurement
remote
scaffold
ui
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: Cordy/cairn-desktop#6
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Depends on Task 5.
Goal
Abstract the Cairn server so the engine can be tested without a network. The real WebDAV + tus implementation arrives in Plan 2.
Files
internal/remote/remote.go,internal/remote/memremote.go,internal/remote/memremote_test.goProduces
The method names map directly onto WebDAV verbs:
List→PROPFIND,Get→GET,Put→PUT/tus,Mkcol→MKCOL,Delete→DELETE,Move→MOVE. Keeping that shape now means Plan 2's real implementation is a thin translation.The ETag contract — this is load-bearing
Remote change detection compares ETags. The ETag must change whenever content changes, or the engine will miss remote edits and silently diverge. In
MemRemote, implement it as a hash of the content, which satisfies the contract exactly.Steps
Putreturns a non-empty ETag and the correct sizeMoverelocates the object and leaves nothing behindPutcreates parent collections implicitlyMemRemote. Map plus mutex; ETag = truncated SHA-256 of content;Getreturns a copy.var _ Remote = (*MemRemote)(nil)git commit -s -m "feat(remote): Remote interface and in-memory implementation"Acceptance criteria
Listreturns sorted, canonical paths.Done
What was built
internal/remote/remote.go:Entrystruct and theRemoteinterface (List,Get,Put,Mkcol,Delete,Move,Stat) matching the issue'sProducesblock, with doc comments for the ETag andfs.ErrNotExistcontracts.internal/remote/memremote.go:MemRemote, an in-memory mutex-guarded implementation; ETag = first 16 hex chars of SHA-256(content);Getreturns a private copy;Putcreates parent collections implicitly.Movenow rejects moving into its own subtree (fs.ErrInvalid) and moving onto an existing file/collection where either side is a collection (fs.ErrExist), matchingMemFS.Move's existing guards instead of silently dropping bytes or merging trees.internal/remote/memremote_test.go: 15 tests total (13 original + 2 added in the fix round).Tests
go test -v ./internal/remote/...— all 15 tests pass, including the issue's five named behaviours and the two newMoveguard tests, each shown red-first against the pre-fix code.go vet ./...andgo test -count=1 ./...clean acrossinternal/remote,internal/sync,internal/vfs;gofmt -l .empty.go vet ./...andgo test -count=1 ./...both pass (ok cairn.ch/desktop/internal/remote,internal/sync,internal/vfs). No coverage step in this workflow.Acceptance criteria
TestMemRemotePutReturnsETagAndSize), different content → different ETag (TestMemRemoteETagChangesWithContent, issue's code verbatim), identical content → identical ETag (TestMemRemoteETagStableForIdenticalContent), Move relocates and leaves nothing behind (TestMemRemoteMoveRelocatesAndLeavesNothingBehind+TestMemRemoteMoveDirectoryMovesChildren), Put creates parent collections implicitly (TestMemRemotePutCreatesParentCollectionsImplicitly).Listreturns sorted, canonical paths —TestMemRemoteListSorted.Rulings
io/fs.ErrNotExist(check with errors.Is) for Get/Delete/Move on a missing path — implemented and tested (TestMemRemoteMissingPathErrorsWrapErrNotExist).git commit -s, plus theCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>trailer — both commits comply.Deferred
Remote.Putdoc's "identical content ⇒ identical ETag" guarantee is one-directional in the issue's actual contract; should be scoped toMemRemoteonly.mkcolParentsLocked, Move to same path) are untested and don't wrap a sentinel error.Delete/Move/parent-creation/notExistare near-verbatim copies ofMemFS, already drifted once (F1)./, trailing/) are stored verbatim, soListcan return non-canonical output for non-canonical input.Implemented and reviewed by Claude (subagent-driven), landed on main after review and green CI.