P2-8: cmd/cairnsync — headless CLI #28
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#28
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-7.
Goal
A headless binary that runs the same engine with no GUI. This is what CI uses for integration tests, what a server administrator can cron, and what you use to debug a customer's problem without a desktop session.
Files
cmd/cairnsync/main.go,internal/config/config.go,internal/config/config_test.goInterface
--once— a single pass, then exit. This is the mode CI uses.--interval— poll loop. Default 30s.--dry-run— print the operationsDecideproduced and exit without executing any of them. This is the single most useful debugging tool you will have; make its output readable.0success,1operational error,2config error,3auth failure. CI branches on these.Config file
CAIRN_SERVER,CAIRN_USERNAME,CAIRN_PASSWORD,CAIRN_LOCAL_PATH), env wins — matching howcairnditself handles secrets.--verbose. Add a test that runs a full pass with verbose logging and asserts the password string appears nowhere in the output.Output
Human-readable by default;
--jsonemits one JSON object per line so CI can assert on it. Print a summary at the end: applied, skipped (with reasons), conflicts.Steps
--dry-runexecutes nothing (assert against a counting remote), the password never appears in verbose output.OSFS+ WebDAVClient+state.Storeintosync.Engine..249dogfood with a throwaway folder.git commit -s -m "feat(cmd): cairnsync headless CLI"Acceptance criteria
cairnsync --oncesyncs a real folder against a real Cairn.--dry-runperforms zero mutations.Done
What was built
cmd/cairnsync:--config <path> [--once] [--interval 30s] [--dry-run] [--json] [--verbose], exit codes 0/1/2/3internal/config: JSON config with env override (env wins), strict validation, password redactioninternal/sync:(*Engine).Plan()— a plan-only path (P2-R9) so--dry-runexecutes nothingTests
go vetclean, all packages pass, coverage total 90.2%)-raceclean; manual smoke test against a local throwaway cairnd (dry run, tus upload of a 12 MiB file, exit 3/2 paths, password-leak scan, loop mode under SIGTERM)TestAFolderGoneBetweenPassesDeletesNothing, both subtests) carries noGOOSskip, so CI run #26 is the Linux evidence (CV5)Acceptance criteria
cairnsync --oncesyncs a real folder against a real Cairn." — met by the local-cairnd smoke test (real OSFS, Client, on-disk state DB, 4 files incl. a 12 MiB tus upload) andTestOnceSyncsBothWays; the.249run is left to the owner (P2-R10), exact command in task-28-report.md.--dry-runperforms zero mutations." —Engine.Planwith counting fakes (every op kind) plusTestDryRunChangesNothing(zero non-idempotent requests, no state DB written).TestThePasswordAppearsInNoOutput(7 runs × 2 modes, stdout+stderr, raw/URL-escaped/base64 forms) andTestConfigNeverPrintsThePassword.--jsonone object per line + summary (applied/skipped-with-reasons/conflicts) — asserted by aneventshelper that fails on any non-JSON-object line.Rulings
Engine.Plan()for--dry-run, tested with counting fakes; plus a test thatstate.Openon a file DSN persists across Close/Open..249needs the owner's app password, which Claude never uses; the local-cairnd smoke test stands in, and the.249command goes in this comment for the owner to run.<server>/dav/home/for Cairn's default per-user homes; documented inconfig.goand the package doc.cairnd-dev.sh(throwaway local cairnd) — never~/Cairn, never.249, never the owner's credentials.GOOSskip so ci.yml's Linux job is the Linux evidence for the fix, since there is no local Linux runtime here.Deferred
within()check, no lock/busy_timeout for overlapping runs, first-signal message,curl -uon the command line) — deferred to the final review; see progress.md for the full list.PlatformWindowshard-coded is a recorded product decision) — pre-existing or explicitly out of scope for this round.Implemented and reviewed by Claude (subagent-driven), landed on main after review and green CI.
The .249 smoke test, for the owner (P2-R10, phase-2 final review X6)
The closing comment above pointed at a local report file; here is the command itself. Run it on your machine, in a cairn-desktop checkout at
b3c84c3or later (v0.2.0-integration is fine). It uses thenikola-testapp password minted in .249's UI (the Cordy/Cairn secretCAIRN_ENC_APP_PASSWORD), which Claude never uses. It syncs a throwaway server folder, so nothing else in the home folder is touched:This assumes .249 keeps cairnd's default per-user homes, so
/dav/home/is nikola-test's home. If--onceexits 3, the app password or the username is wrong for WebDAV. If the second--onceapplies anything, suspect the S3 ETags — that is exactly what this run is for.Every curl reads the credentials from a config on its stdin (
-K -, fed by the shell's builtinprintf), never from its command line, wherepswould show them. A password holding"or\must be escaped with a\inside that quoted config value.Posted by Claude on behalf of @Cordy to complete ruling P2-R10.