git_seed: git.seed.export bundles a seed's changes back (git-seed-export-v1)
Piece 6 (machine side) of the Cloud project seed: bring a seeded Cloud folder's changes back to the Mac as a diff, not a folder copy.
Leo's words:
Your project folder is copied up.
we'll want to deal with the edge cases for this too
Spec: leo-workspace design/cloud.md, "Getting the project onto the machine", rule 6 ("Bringing changes back is a diff too").
What it adds
- A new op,
git.seed.export {folder, since_work, since_head, bundle}, under a new capability,git-seed-export-v1. It is not added togit-seed-v1because a machine advertising onlygit-seed-v1would deny the new op at its remote gate. The Mac needs a capability it can ask for first (machine_gate::outdated). The link'sFsGateadmits the op throughgit_seed::frame_command.build_capabilitiesleaves the new capability out, as it already does forgit-seed-v1, because whether a host serves it depends on the host. - A job, not synchronous. Making the INDEX and WORK commits runs
add -Aover the whole working tree, which can take a while on a large repository. So the op answers{job}at once, likegit.seed.start, andgit.seed.statusreads it. Once the job is done its status adds{head_ref, head, index, work, stash, unchanged, bundle_bytes, left_out}. - The machine makes the same INDEX and WORK commits the Mac's seed plan makes: the same trees, the fixed author
cmux seed <seed@cmux.invalid>, the fixed date946684800 +0000, and the same messages. The shared code lives ingit_seed/snapshot.rs, which delta seeds'check_deltanow uses too. The "untracked secret" rule moved tocmux_tui_core::git_seed::secrets, and the Mac'slink/seed/secrets.rsre-exports it. Both sides leave out the same.env*files, keys and nested repositories, so the same state gives the same commit, and an unchanged machine answersunchangedwith no bundle written. - The bundle carries
refs/cmux-back/{head,index,work,stash?}with the prerequisites^since_workand^since_head. The seed's trees are excluded too, so it holds only the files that changed. It is written under the fs roots through fs-v1's atomicPendingWrite(create-new, 0600, free space checked), outside the seed's folder. The commits are also recorded in the folder asrefs/cmux-back/*, which keeps them out of gc, and a later delta seed may build on an export:check_deltaacceptssinceequal torefs/cmux-seed/workor torefs/cmux-back/work. - It keeps the same sandbox, path rules and hooks-off git settings as
git.seed.start. Ignored files (node_modules, build output) never go into the bundle. CMUX_TEST_GIT_SEED_REMOTESis a test seam for debug builds only: it readshttps://github.com/O/N.gitasfile://<dir>/O/N.gitso a journey test can seed a real daemon without the network. Release builds never read it.
The Mac side, cmux link seed pull, is the follow-up PR stacked on this one.
Tests
cargo test -p cmux-tui-core --lib git_seed on the fleet (cmux-ci test). New tests in git_seed/tests/export.rs:
- An export of an unchanged seed is the Mac's exact WORK commit, and no bundle is written.
- A commit, an edit, an add, a delete and a staged change all come back. Ignored files,
.envand a nested repository stay out. The new commits form a WORK, INDEX, HEAD chain, the same state exports to the same commits, and the bundle is 0600. - A delta seed builds on an export.
- Refused cases: a bundle path inside the folder, a folder that is not a seed, a bad sha, and a
sincethe folder lacks.
Clippy with -D warnings is clean in the files this PR touches. The remaining clippy errors on the base are in server/agent_session_folders.rs and cli/cloud_repo.rs and are not from this PR.
Agent session: agent session
🤖 Generated with Claude Code
Summary by cubic
Adds the machine side of the Cloud project seed's rule 6: a seeded folder's changes can now be bundled back to the Mac as a diff (git.seed.export), not a folder copy.
- Runs under a new capability,
git-seed-export-v1, kept separate fromgit-seed-v1so machines advertising only the older capability deny it at their remote gate. - Exports are jobs read through
git.seed.status; an unchanged seed answersunchangedand writes no bundle, and a retried export whose answer was lost returns the same job even once its bundle exists. - Only a seed's folder exports; repositories without the seed record are refused, and the bundle path must be outside the folder.
- The machine makes the same INDEX and WORK commits the Mac's seed plan makes (shared snapshot code, fixed identity and date, shared messages), so the same state always yields the same commits.
- The bundle carries
refs/cmux-back/{head,index,work,stash?}with^since_workand^since_head, so only changed files travel; it is written atomically with 0600 permissions. - Comparison is against the newest record (
refs/cmux-backpreferred overrefs/cmux-seed): a stash-only change still exports, a dropped stash sends nothing, andsinceneed not be the newest record since the Mac can pull again from its own base. - The commits are recorded as
refs/cmux-back/*in the folder, keeping them out of gc and letting a later delta seed build on an export. - The untracked-secret rule moved to
cmux_tui_core::git_seed::secretsso both sides exclude the same.env*files, keys, and nested repositories, including secrets with non-UTF-8 names; an unreadable registry file now counts as holding a token. - Work inside a dirty submodule is refused (
seed.submodule_local_work), never exported as unchanged. CMUX_TEST_GIT_SEED_REMOTESis a debug-only seam for journey tests; release builds never read it.
The Mac side, cmux link seed pull, is the follow-up PR stacked on this one.