Task 12: The engine cycle #12
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#12
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 11. Wires everything into one pass.
Files
internal/sync/engine.go,internal/sync/engine_test.goProduces
The cycle
FS.Walk()andRemote.List(); loadState.All().Sidevalues, callDecide.DetectRenamesover the resulting operations (Task 11).Orderthem (Task 8).Rules
Skip, not a returned error. Only a failure that makes further progress impossible (a failedWalkorList) returns an error. One bad file must not stop the folder.record()runs after confirmation, so a crash mid-sync resumes rather than corrupts.ConflictName(path, e.Now()), download the remote to the canonical path, upload the conflict copy, and append toResult.Conflicts. Both versions survive.Steps
SyncOnceis a no-op (len(Applied) == 0) — proves idempotenceSkipped, and no error returnedWrite a
newEngine(t)helper returning(*Engine, *vfs.MemFS, *remote.MemRemote)withNowpinned to a fixed time and an in-memory state store.git commit -s -m "feat(sync): engine cycle — scan, decide, order, execute, record"Acceptance criteria
SyncOnceapplies zero operations.SyncOncereturns an error only whenWalkorListfails.internal/syncstill imports nothing forbidden.Done
What was built
internal/sync/engine.go:Engine/Skip/Result/SyncOncerunning Walk+List+State.All → union of paths → per-path Decide → DetectRenames → Order → execute-then-record.Result.Conflicts) with collision numbering that never overwrites an existing local/remote/state path.Stat(closes a window where a concurrent write could be hidden).Tests
TestSyncSecondPassIsNoOp,TestSyncConflictCopyNeverOverwritesExistingPath,TestSyncMoveNeverHidesAConcurrentWrite,TestSyncReturnsErrorWhenScanFails(walk/list/state), plus the arch guard.linux/arm64— green (go vet+go test -count=1 ./..., all 4 packages ok).Acceptance criteria
SyncOnceapplies zero operations — met,TestSyncSecondPassIsNoOp.SyncOncereturns an error only whenWalkorListfails — met as read (State.All failure also errors, since without state rows every path degrades to a two-way compare); now covered by astatesubtest.internal/syncimports nothing forbidden — met,TestEngineHasNoForbiddenImportsgreen.Rulings
io/fs.ErrNotExistfor a missing path; the engine treats it on delete as already done.ConflictNamegets a number inside the parenthetical.git commit -s, with theCo-Authored-Bytrailer.settleMoverecords the prior row's remote metadata — what the scan proved before the MOVE — never a post-moveRemote.Stat, so a concurrent write is never hidden as "synced."Client.Movecreates missing ancestor collections before sending MOVE.Deferred
engine.go:487— settleMove's Put-failure branch drops theRemote.Staterror/not-found result from the Skip reason.engine.go:361-363— a failed download inside a conflict leaves the copy unreported inResult.Conflicts; next pass re-conflicts it.engine_test.go:410— coverage gaps on rare data-integrity branches (state-only R4 row, failed conflict-copy upload).engine.go:396— nilEngine.Nowpanics on the first conflict; default totime.Nowor document the requirement.engine.go:311-314— the Stat/not-found-to-ErrNotExist idiom is duplicated three times; a small helper would remove the drift risk.State.All.Implemented and reviewed by Claude (subagent-driven), landed on main after review and green CI.