Skip to content

Sync → Manifest can no longer record content that was never uploaded#107

Merged
revett merged 2 commits into
mainfrom
revett/fix-manifest-not-upload-issue
Jul 21, 2026
Merged

Sync → Manifest can no longer record content that was never uploaded#107
revett merged 2 commits into
mainfrom
revett/fix-manifest-not-upload-issue

Conversation

@revett

@revett revett commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Resolves #84

Problem

  • The manifest uploaded at the end of a sync was a fresh snapshot of the vault taken after the plan ran, so a file edited or created while the sync was running was recorded with content the bucket never received
  • The change then never uploaded, since state.json and the manifest already agreed, and a later sync from another device could push the stale bucket copy back over it; the edit was lost with no conflict copy and no error

Changes

  • Derive the manifest from the plan's outcome instead of a disk snapshot, so it only ever claims content the bucket actually holds
  • Keep the post plan snapshot solely to refresh local file stats where content still matches, so unchanged files keep skipping rehashes
  • Add unit tests covering every action kind, plus an end to end regression test reproducing the mid sync edit

Why

  • A file changed mid sync now keeps its bucket entry in the manifest and state.json, so the next pass sees a plain local change and pushes it
  • The only race left understates the bucket rather than overstating it, which the next pass repairs by pushing again; the direction that loses data is gone by construction

Greptile Summary

This PR fixes a data-loss bug (#84) where the manifest uploaded at the end of a sync was derived from a fresh disk snapshot rather than from what the plan actually did to the bucket. A file edited mid-sync could land in the manifest with content the bucket never received, silently stalling all future uploads of that edit and leaving the vault vulnerable to a remote device pushing a stale copy back over it.

  • manifestAfterSync (new, plan.ts) derives the post-sync bucket state directly from the plan's actions applied to the pre-sync remote snapshot, so the manifest can never claim content the bucket doesn't hold.
  • adoptLiveStats (new, sync.ts) follows up with a re-snapshot purely to refresh size/mtime for stat-skipping on the next pass, only adopting live entries whose hash still matches the manifest; mid-sync edits and creations are left as they appear in the bucket.
  • Unit tests cover every action kind in manifestAfterSync, all four adoptLiveStats branches, and an end-to-end regression test that intercepts a putObject call to simulate a mid-sync edit and verifies both the manifest written and the successful re-push on the next pass.

Confidence Score: 5/5

Safe to merge — the change is a well-scoped fix to a clearly understood bug, guarded by both targeted unit tests and an end-to-end regression test that reproduces the exact failure mode.

The new manifestAfterSync logic is straightforward: it starts from the remote snapshot and applies only the plan's actions, so every branch (push, pushDelete, pull, pullDelete, and all three conflict variants) has a verified correct outcome. The adoptLiveStats function iterates only manifest entries, so it cannot accidentally add mid-sync creations to the manifest. The e2e test's putObject interception closely mirrors the real race, and the assertions cover both the manifest written after the first pass and the successful catchup on the second pass. No pre-existing invariants are broken.

No files require special attention.

Important Files Changed

Filename Overview
src/sync/plan.ts Adds manifestAfterSync, which correctly derives the post-sync bucket state from plan actions against the pre-sync remote snapshot, handling all five action kinds without re-snapshotting disk.
src/sync/sync.ts Replaces the post-plan takeSnapshot used for the manifest with manifestAfterSync + adoptLiveStats; exports adoptLiveStats for unit testing. Logic is correct — stat adoption only fires when hashes match, so mid-sync edits keep the bucket entry.
src/sync/plan.test.ts Adds six manifestAfterSync unit tests covering push, pushDelete, pull/pullDelete (no-op), and all three conflict variants; assertions match the expected bucket state for each case.
src/sync/sync.test.ts Adds an e2e regression test that faithfully reproduces #84 (mid-sync edit intercepted via a patched putObject), plus four isolated adoptLiveStats unit tests covering the content-match, mid-sync edit, mid-sync deletion, and mid-sync creation branches.

Reviews (2): Last reviewed commit: "Address comments" | Re-trigger Greptile

Comment thread src/sync/plan.ts
Comment thread src/sync/sync.ts Outdated
@revett
revett merged commit 0671c81 into main Jul 21, 2026
10 checks passed
@revett
revett deleted the revett/fix-manifest-not-upload-issue branch July 21, 2026 20:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Manifest can record content that was never uploaded

1 participant