P4-1: Placeholder abstraction and pin model (no OS code) #44
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#44
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?
First task of phase 4. Depends on
phase-3-desktop-app.This is the phase-1 trick applied again: put everything platform-independent behind an
interface first, with a fake implementation, so the hard logic is tested on the arm64 Pi runner
before any OS-specific code exists. Three separate OS integrations follow (P4-2, P4-3, P4-4);
none of them should contain sync logic.
Files
internal/vfs/placeholder.go,internal/vfs/fakeplaceholder.go,internal/vfs/placeholder_test.gointernal/state/state.go(pin state column)Produces
FileInfo.Hydratedwas added in Task 5 (see its amendment comment). Pin state persists in thestate DB — it is user intent and must survive restarts and re-syncs.
The rules the engine already follows
Both were added upstream so phase 4 adds no engine logic:
are not there.
upload.
If implementing an OS provider tempts you to change
internal/sync, stop — the design iswrong somewhere.
The failure mode that defines this feature
Accidental mass hydration. Any code path that reads content for every file — hashing,
indexing, a naive
Walkthat opens files, an antivirus scanner, a backup tool, Spotlight —downloads the entire Cairn. The user asked for files-on-demand precisely to avoid that, and
they may be on a metered connection or a 256 GB disk.
zero hydrations. This is the single most valuable test in phase 4.
Steps
PlaceholderFSandPinState.pin_statecolumn to the state store; defaultPinAuto.FakePlaceholderFS— in-memory, counts hydrations, so tests can assert on them.Hydrated: false; hydrate →trueandcontent readable; dehydrate →
falseand content gone;DehydrateonPinAlwaysfails;pin state survives a store reopen; a full sync pass over placeholders causes zero
hydrations and zero operations.
git commit -s -m "feat(vfs): placeholder abstraction and pin model"Acceptance criteria
PinAlwaysfiles cannot be evicted.Amendment — 2026-09-11: what #44 inherits from phase 2 (phase-2 final review X1, X2, and Task 26 F4)
Phase 2 put the placeholder rule into the engine (#26): a file with
Hydrated == falseis neveropened or hashed, presents as the content last synced, and is never uploaded. Four points from the
phase-2 reviews bind this issue. The first was carried here only in #26's closing comment, so it is
restated here.
1. A renamed placeholder whose FileID is unknown loses its only copy (Task 26 F4)
DetectRenamespairs a delete with an upload only on a known FileID (internal/sync/rename.go:24,:44-45). Suppose a placeholder is renamed locally and the provider reports no FileID (""). Then:OpDeleteRemote;OpUpload, whichputrefuses because the file is aplaceholder (
notOnThisDevice,internal/sync/engine.go:726-728).The server copy is deleted. The bytes are then nowhere, because a placeholder holds none.
Required. A pass never deletes the server copy of a synced path while a placeholder that could be
its renamed self is unpaired. The simplest form is this: when the scan finds a placeholder with no
state row (only a rename or a provider bug makes one), every unpaired
OpDeleteRemotein that passbecomes a Skip naming both paths, until the provider reports a FileID or the new path is hydrated.
Test. Use
FakePlaceholderFS. Rename a synced placeholder with FileID"". Assert that noOpDeleteRemoteis applied, that the server still holds the file, and that the pass hydratesnothing.
2. The content check before a local delete or download never reads a placeholder (final review X1)
If final review X1 lands as proposed, the engine reads a local file before it deletes it or downloads
over it, whenever the state row vouched for that file without a read. That re-read must skip a
placeholder, because a placeholder cannot have been edited here. Extend this issue's "1,000
placeholders, zero hydrations" test with a server delete and a server edit of placeholders. It must
still show zero hydrations: the delete removes the placeholder, and the edit updates it.
3. A dehydrated tree is not an empty folder (final review X2)
If final review X2's guard lands, the engine refuses a pass when one side lists nothing while the
state store holds rows. A tree of placeholders must count as present entries, never as an empty
folder.
4. OSFS on Windows opens every entry during Walk (#45)
fileID(internal/vfs/fileid_windows.go:20-31) takes the FileID throughroot.OpenplusGetFileInformationByHandle, which opens a handle with read access for every file Walk lists. Thisissue names exactly that failure mode: "a naive
Walkthat opens files". Before #45 ships, confirmthat opening a Cloud Filter placeholder this way neither hydrates nor recalls it. Otherwise take the
FileID from a handle opened with
FILE_READ_ATTRIBUTESonly. Run the "zero hydrations" test onWindows through
OSFS.Walk, not only through the fake.