* davsync: a damaged file on the server is rebuilt, not failed on forever
When the server's backup can't be read as a backup at all — a write a
relay or tunnel cut short, or one damaged since — syncOnce returned the
error and stopped. This computer's own setup is fine, so it could never
be pushed over the unreadable file to fix it, and every sync after
failed the same way: the only way out was deleting the file by hand.
(#531 detects a short write as it happens, but a file already damaged —
by an older magpie without that check, or by anything since — stayed
stuck on "not a magpie backup".)
The rebuild merges; it never pushes this computer's bundle as it is. A
sync that finds the file unreadable falls back to the last good copy of
the server's file it read whole — its cache, when that copy is
trustworthy — and runs the normal three-way merge over it, so another
computer's newer setup the damaged write swallowed survives. Three
guards say when a computer may do that, and any one missing leaves the
error standing: it has synced before (a fresh computer has no last-good
copy and no right to rebuild what it never saw); the cached copy is the
version the damage replaced (the damaged body is a prefix of it, which
is what a cut-short write leaves — and what an out-of-date computer's
older copy is not); and the body really begins like a magpie backup, so
a server answering with a page (a captive portal) is reported, never
rebuilt over. backup.ErrCorrupt — the message those two damaged checks
already gave — marks that case, told from a wrong passphrase and from a
newer magpie's file, both still refused. The push carries the damaged
file's ETag, so it stays checked against the server, and it happens
even when the merge matches the cached copy: the server's file is the
damaged one and must be replaced. The unreadable body is kept aside
under the sync folder, the newest few, so a server that always answers
the same unreadable way doesn't pile one up per tick.
Tests: TestSyncRebuildsFromCachedCopy pins the self-heal (the server
ends whole, the damaged body kept aside); TestSyncFreshComputerDoesNot
Rebuild and TestSyncOutOfDateComputerDoesNotRebuild pin the two
data-loss cases — the out-of-date one fails without the prefix guard
(verified by mutation); TestSyncPageIsNotDamage pins that a 200 HTML
page is reported, not pushed over; the newer-magpie refusal is guarded
against regression. go vet, and go test -tags nogui ./internal/davsync
./internal/backup under a temp HOME pass, as do the linux and windows
builds.
* davsync: a body cut inside the sealed file's constant head rebuilds nothing (yetone on #607)
The prefix check alone let an out-of-date computer rebuild: Seal writes
the envelope with MarshalIndent, so every version begins with the same
constant head up to the salt, and a body cut inside it (40 or 90 bytes)
is a prefix of every version — including an older cached copy — so that
copy's computer passed the guard and pushed its merge over a newer one's
data. The guard now also requires the damaged body to reach past the
random fields, to the "data" key that follows the salt and nonce, which
ties it to the one version it was cut from. A body cut before them is
attributable to no version, so it stays an error for every computer,
even the one that saw the good version — fail loud rather than rebuild
blind; a relay cutting a real write cuts deep into the data, so legit
rebuilds still go. TestSyncOutOfDateComputerDoesNotRebuild now cuts at
half, 90 and 40 bytes; the two short cuts fail against the old guard
(the out-of-date computer rebuilds and kimi is lost).
Co-Authored-By: Claude Code <noreply@anthropic.com>
---------
Co-authored-by: Claude Code <noreply@anthropic.com>