Task 5: Filesystem interface and in-memory implementation #5
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#5
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 4.
Goal
Abstract the filesystem so the engine can be driven entirely in memory. The real OS filesystem arrives in Plan 2.
Files
internal/vfs/vfs.go,internal/vfs/memfs.go,internal/vfs/memfs_test.goProduces
Contract implementations must honour
Walkreturns sorted paths, all canonical (seesync.Normalise).Writecreates parent directories implicitly.MovepreservesFileID— local rename detection (Task 11) depends on this.Removeon a directory removes its children too.Statreturns(_, false, nil)for a missing path — missing is not an error.FileIDmeans "unknown" and must never be treated as a match.Steps
FileIDWrite("x/y/z.txt", …)createsx/yMovepreservesFileID, target exists, source is goneWalkoutput is sortedMemFS. Back it with amap[string]*nodeplus a mutex; assignFileIDfrom a monotonic counter.Openmust return a copy of the data so callers cannot mutate stored content.var _ FS = (*MemFS)(nil)git commit -s -m "feat(vfs): FS interface and in-memory implementation"Acceptance criteria
MemFSis safe for concurrent use.Amendment — 2026-09-10: files-on-demand is now in scope
Virtual files moved from "decided against for v1" to a planned phase (
phase-4-virtual-files).That changes this task, so read this before implementing.
Add a hydration flag to
FileInfoWhy it has to go in now
Without it, the engine has no way to distinguish "a file whose content I can read" from "a
file whose content would have to be downloaded to read". It would then hash placeholders to
detect changes — hydrating the entire tree on the first sync pass and defeating the whole
feature.
Adding the field now costs one line. Adding it later means changing an interface that four
packages depend on, plus every test that constructs a
FileInfo.For this task specifically
MemFSsetsHydrated: trueunconditionally. It has no placeholder concept.OSFS(P2-5) will do the same. Only the phase-4 providers ever returnfalse.Done
e39dc13c46— feat(vfs): FS interface and in-memory implementation5d99e3fe78— fix(vfs): MemFS.Move rejects unsafe destinationsWhat was built
internal/vfs/vfs.go:FileInfoand theFSinterface (Walk, Open, Write, Mkdir, Remove, Move, Stat), verbatim to the issue's signatures.internal/vfs/memfs.go:MemFS, a mutex-guarded in-memoryFSwith monotonicFileIDs;Openreturns a copy of stored bytes.internal/vfs/memfs_test.go: 15 named tests covering every contract point.Movenow rejects the three unsafe destinations a real filesystem would reject (self-move no-op; into-own-descendant wrapsfs.ErrInvalid; onto an existing path where either side is a directory wrapsfs.ErrExist) instead of silently corrupting the tree.Tests
go vet ./...andgo test -count=1 ./...green locally (darwin) and in CI (linux).-cover).Acceptance criteria
TestMemFSConcurrentUsepasses under-racelocally; CI's plain run still catches an unlocked map via a runtime panic.var _ FS = (*MemFS)(nil)— present.Rulings
io/fs.ErrNotExistfor Open/Remove/Move on a missing path — implemented via&fs.PathError.git commit -s,Co-Authored-Bytrailer — both commits comply.undefined: NewMemFS) — satisfied.-raceevidence is local-only (CI can't run-racewithout cgo on linux); the plain CI run still catches an unlocked map — satisfied for #5.Deferred
fmt.Errorfstrings wrapping no sentinel.Implemented and reviewed by Claude (subagent-driven), landed on main after review and green CI.