Task 3: Path normalisation (NFC, separators) #3
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#3
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 2.
Goal
One canonical path form used everywhere in the engine: forward slashes, no leading or trailing separator, no duplicate separators, NFC-normalised.
Why this is a data-integrity task
macOS returns NFD from the filesystem; Linux and Windows use NFC. A file named
cafécreated on a Mac and one created on Linux are different byte strings. Without normalising at the boundary they sync as two separate files forever. This is the most common duplicate-file bug in sync clients.Files
internal/sync/path.go,internal/sync/path_test.goProduces
Depthcounts path segments and is used later to order creates parent-first and deletes child-first.Steps
undefined: Normalise.golang.org/x/text/unicode/norm—norm.NFC.String(...). Order matters: fix separators first, then normalise.golang.org/x/textis permitted by the arch guard.git commit -s -m "feat(sync): canonical path normalisation with NFC"Acceptance criteria
Normaliseis idempotent for every input.Done
What was built
internal/sync/path.go:Normalise,Join,Parent,Depthper the issue's Produces listNormalisefixes separators first (\→/, drops empty/.segments), then runs NFC viagolang.org/x/text/unicode/norminternal/sync/path_test.go: the issue's three tests verbatim; the NFD case written with explicit\uescapes (not literal glyphs) per ruling R8, so it can't be silently renormalised into a vacuous testgolang.org/x/textv0.34.0 (its owngo 1.24.0directive) sogo.modstays exactlygo 1.25with notoolchainline (R1)Tests
TestNormalise,TestNormaliseIsIdempotent,TestParentAndDepth, plus Task 2'sTestEngineHasNoForbiddenImportsall passNormalise30s / 4.58M execs against idempotence + separator/NFC invariants — no failuresAcceptance criteria
Normaliseidempotent for every input — issue's 3 cases pass; structurally true (output is always free of\, leading/trailing/duplicate/,.segments, and NFC is idempotent on NFC text); confirmed empirically by the reviewer's fuzz runTestEngineHasNoForbiddenImportspasses;x/textis explicitly permitted by the issueRulings
go.modkeeps exactlygo 1.25, notoolchainline; newestx/textwhose own go directive is ≤ 1.25 → v0.34.0 used\uescapes so it genuinely exercises NFD→NFCgit commit -s,Co-Authored-By: Claude Opus 5trailer\→/mapping stands as #3 mandates; binding on phase 2's boundary adapters (#22, #25) to refuse native\names and detect normalise-collisions before they reach the enginepath.goin a clean clone givesundefined: Normalise, matching the issue's expected failure)Deferred
Joinis exported but untested; itsbase == ""branch is redundant..segments pass through unrejected; needs a ruling on where to reject (likely a phase-2 adapter)Implemented and reviewed by Claude (subagent-driven), landed on main after review and green CI.