P2-2: PROPFIND — implement Remote.List and Stat #22
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#22
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 P2-1.
Goal
Enumerate the remote tree over WebDAV and satisfy the
Remote.List/Remote.Stathalf of the interface from Task 6.Files
internal/remote/propfind.go,internal/remote/propfind_test.goWhat to request
PROPFINDwithDepth: 1, descending recursively. Request these properties:The two
quota-*properties are RFC 4331 and are what let the UI show free space (Cairn already serves them).Parsing gotchas — each gets a test
207 Multi-Status, not 200. Treat any other 2xx as an error.hrefvalues are URL-encoded and may be absolute or relative. Decode, strip the base path, then pass throughsync.Normalise."abc123") and may carry aW/weak prefix. Strip both, consistently, or every comparison against stored state will mismatch and you will re-download the world.getlastmodifiedis RFC 1123 (Mon, 02 Jan 2006 15:04:05 GMT), not RFC 3339.<d:collection/>inside<d:resourcetype>and no content length. Map toEntry.IsDir = true.Listoutput, or every directory contains itself.Steps
httptestserving a captured real 207 body — generate one from the.249dogfood and paste it into a testdata file. Do not invent the XML; real servers differ from the spec in small ways.W/weak ETag, a path with a space, and a non-ASCII filename.encoding/xml.git commit -s -m "feat(remote): PROPFIND-backed List and Stat"Acceptance criteria
List.Listoutput is sorted and canonical, matching theMemRemotecontract so the engine cannot tell them apart.Amendment — 2026-09-11: phase-1 hand-off (binding rulings Task 3 F1 and F5)
The phase-1 review made rulings that bind the adapters feeding names to the engine. They
amend this issue's gotcha "Decode, strip the base path, then pass through
sync.Normalise", which on its own lets distinct server names collapse into one engine path.Task 3 F1 — names that cannot be normalised safely
For every URL-decoded href segment:
\is never passed toNormalise.Normaliseturns\into/, sonotes\draft.txtwould become the different pathnotes/draft.txt. Skip theentry and report it per file, with a legible reason. It cannot exist on Windows either (spec §4:
"detect, skip, report — never fail the whole sync").
path. This covers backslash twins, NFC/NFD twins, and NFC compatibility singletons (U+F900
becomes U+8C48). Skip every member of such a group with a reason, as #14 does for case
collisions. Never let one entry shadow the other.
a deletion. An entry that is simply left out of
Listwhile it has a state row reads as"deleted on the server", and the engine would then delete the local copy.
Engine support, since commit
e7cef2d(fix(sync): refuse non-canonical paths and their canonical twins):SyncOncerefuses every listed path that is not in canonical form (Normalise(p) != p). Italso refuses the canonical path that shares its normal form, each as a Skip. An adapter can
therefore meet (c) by listing a refused entry under its raw decoded name. Omitting it does not
meet (c).
Task 3 F5 — keep the native name
Normalisealso rewrites names that are not NFD (compatibility ideographs, the Stream-Safeinsertion after 31+ combining marks). A byte-preserving server can therefore hold names that
the NFC engine path does not reproduce.
engine-path → native-name map, or the equivalent in state.
Statand, in #23,GET/PUT/DELETE/MOVEon an existing entry use that native name.URL for an NFD-stored name returns 404, which under R2 reads as a normal race one step away
from a deletion.
Note — paths outside the sync folder (final review X1)
Since commit
6a9a06b(fix(sync): refuse paths that could resolve outside the sync root), the enginerefuses on every platform any path that is empty, starts with
/, or has an empty,.or..segment. It reports each one as a Skip.
The adapter should still never resolve
..itself: an href that decodes to something outsidethe base path is not an entry of the sync folder. The same applies after
%2e%2eand%2fdecoding.
Done
What was built
Client.List/Client.Stat/Client.Quotaover PROPFIND, matching theMemRemotecontract (sorted, canonical, no self entry).normaliseETaghelper for every entry; RFC1123 dates;<collection/>→IsDir; absolute/relative/encoded hrefs handled without ever resolving...Entry.Unreadable+ engine skip (P2-R14/P2-R15): a folder the server cannot address (400/404/"answer doesn't describe it") is Skipped with everything inside it, never emptied and never deleted from.internal/sync/names.go(P2-R14):.cairn-meta.json,.cairn-state,.cairn-upload-*are refused before observe/Decide on both sides.Tests
cairnd-dev.sh, never .249) plus labelled edited copies for cases it can't produce (W/ ETags, hostile hrefs, twins).gofmt,go vet(host, windows/amd64, linux/arm64),go mod tidy -diffclean; full suite green locally each round.9cef60d, arm64, green —go vet ./...andgo test ./...pass for remote/state/sync/vfs, total coverage 91.6%.Acceptance criteria
normaliseETag, reached only viaentryFromPropsfor both List and Stat.List— met: only direct children counted; self falls out by depth and is required to be present (F1 fix).Listsorted and canonical, matchingMemRemote— met:TestListMatchesMemRemote,TestListReturnsTheCapturedTreeSortedWithoutSelf.nativeNamesmap...; hostile-encoded hrefs (%2e%2e,%2f) excluded — met.Rulings
ErrNotFoundandfs.ErrNotExist; MKCOL 405 → nil (kept in mind for #23).<server>/dav/home/;Config.BaseURLis that URL.%are double-decoded → a per-file/per-folder Skip, never a failed pass.cairnd-dev.shonly; never~/Cairn, never .249, never owner's credentials.Deferred
Getmakes one assertion partly vacuous (F5); root-level errors readremote: PROPFIND : …(F6); exact-match native-name map handed off to #23 (F7); cairn-server-notes.md correction (ruling 7) still pending for the controller. None of these affect correctness of what shipped.Implemented and reviewed by Claude (subagent-driven), landed on main after review and green CI.