Delta sync: large files move as content-defined chunks (#161)

* feat(sync): delta sync — large files move as content-defined chunks

Files over 4 MiB push as chunks/<sha256> pieces plus a manifests/<sha256>
chunk list keyed by the whole file's hash, so Op.Blob alone locates it and
the journal format is byte-identical. A 1-byte edit to a 20 MiB file now
transfers ~2 MB instead of ~21 MB, both directions; chunk boundaries come
from a rolling hash (restic/chunker), so front insertions stay cheap.

The hub reassembles whole blobs on demand (spool, verify, backfill, serve),
which is the entire backward-compatibility story: old clients ask for
blobs/<sha> and never learn anything changed. Proven by e2e tests that build
the real pre-change binary from the pinned merge-base commit.

The push skip-proof is one Exists per chunk — three cheaper proxies (local
basis, manifest existence, stored manifest content) each proved false or
forgeable across four CTO review rounds and are recorded in the code
comment. Hub-side, manifests are write-once and must name only chunks the
store holds; reassembly is bounded at 256 MiB against amplified manifests.

Also: per-file sync ceiling 32 -> 100 MiB; import refuses archives whose
journals reference content they do not hold (--allow-incomplete overrides).

Deploy hubs before clients: old hubs refuse chunk keys (push degrades to
offline-retry), and old clients cap reads at 32 MiB so 32-100 MiB files
report "blob corrupt on remote" until the client upgrades.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R6nqxi5a9qcENmJvrgBJF7

* fix(test): fetch the pinned pre-delta commit on shallow CI clones

buildOldBinary archives the pinned merge-base sha, which a fetch-depth-1
actions/checkout does not have — all three old-binary e2e tests failed in
CI with exit 128 while passing on any full local clone. On archive failure,
fetch just that commit (--depth=1, one object; actions/checkout persists
credentials so the in-job fetch works) and retry. Verified against a real
GitHub shallow clone: archive fails, the single-sha fetch succeeds, archive
then yields the pre-delta tree.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R6nqxi5a9qcENmJvrgBJF7

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Snow Lee (Sungwon)
2026-08-13 07:18:37 -07:00
committed by GitHub
co-authored by Claude Fable 5
parent 33ca0caeab
commit 0dd474baab
29 changed files with 3189 additions and 46 deletions
+5 -1
View File
@@ -264,7 +264,11 @@ creates a NEW project (it never joins an existing one by name — pass `--name`
if the archive's name is taken), and the destination hub needs uploads
enabled. A single file in the archive may spool at most 256 MiB to local disk
during import; `--max-blob` raises that if the project really holds a bigger
file. Shares,
file. Import refuses an archive whose journals reference content the archive
does not hold — the shape a pre-delta-sync `bdrive export` produces against a
newer hub, where large files live as chunks the old binary doesn't know to
collect; re-export with a current `bdrive`, or pass `--allow-incomplete` to
import anyway (the missing files are listed and stay missing). Shares,
invite links, and read-heat stay behind (they belong to the hub, not the
project store). Step-by-step walkthrough:
[Migrate between hubs](/reference/migration/).
@@ -91,3 +91,11 @@ Nothing is keyed by folder path, which is why moves and renames are free.
```
Also here for a running project: `daemon.pid` and `daemon.log`.
The hub's storage adds two key classes the local store never holds: files
larger than 4 MiB travel as content-defined `chunks/<sha256>` pieces plus a
`manifests/<sha256>` chunk list keyed by the whole file's hash (delta sync —
a small edit to a large file uploads roughly one chunk, not the file). Local
blobs stay whole; chunking exists only on the wire and in the hub's store,
and the hub reassembles a whole blob on demand for any client that asks for
`blobs/<sha256>`, so older clients keep working unchanged.