Skip to content

GitSync: no-op sync never advances the conflict horizon; bidirectional prune protects server additions (#677, #604) - #895

Merged
rbuergi merged 2 commits into
mainfrom
fix/677-604-gitsync-horizon
Aug 7, 2026
Merged

rbuergi merged 2 commits into
mainfrom
fix/677-604-gitsync-horizon

Conversation

@rbuergi

@rbuergi rbuergi commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #677 and #604 — the two remaining routes to GitSync server-side data loss after #675.

#677 — split the baseline fields by meaning. ReimportAtCommit advanced LastSyncedAt (the conflict horizon behind PreservesServerCopyOf) on ANY Preserved == 0 outcome — including the fingerprint no-op. That disarmed two-way protection, so a later push pruned server-side additions the sync had verified nothing about. Now: a Skipped update records only LastSyncCommitSha (seen-commit marker — up-to-date display and diff base stay correct; a node-free repo commit doesn't leave the space "behind"); only a reconciling import advances the horizon. Field semantics documented on GitHubSyncConfig.

#604 — prune is loud and horizon-aware for bidirectional spaces. Successful prunes now log every path (server log + 🗑 Pruned {path} activity lines + names in the terminal summary). ImportConflictPolicy.PreserveServerAdditions (on for SyncDirection.Bidirectional, reusing #675's kept-from-prune machinery) stops the mirror-prune of uncommitted server additions; ImportOnly mirrors keep FullReplace semantics, pinned by a guard test.

Verification

  • Fail-without (new tests against pre-fix src, trx retained): horizon advanced on a Skipped update; Bidirectional server addition pruned; no Pruned activity line — 3 failed, mirror-guard passed.
  • With the fix: MeshWeaver.GitSync.Test 157/157, MeshWeaver.Graph.Test 820/820; independently re-ran the new classes (6/6). dotnet build -c Release -warnaserror clean on Graph, GitSync, both test projects, and the Memex.Portal.Monolith umbrella.

Known inherited residual (documented in the code): with a frozen horizon, import-written nodes are counted preserved on later skips until committed back — #675's documented false-conflict made visible, not created; the per-node import-write-time manifest remains #677's deferred follow-up.

No What's New entry: data-integrity bug fix.

🤖 Generated with Claude Code

Copilot AI lite review requested due to automatic review settings August 7, 2026 07:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens GitSync against two remaining server-side data-loss routes by (1) preventing no-op imports from advancing the two-way conflict horizon and (2) making prune behavior horizon-aware for bidirectional Spaces while improving prune auditability.

Changes:

  • Split “seen commit” vs “reconciled horizon” semantics: skipped/no-op reimports now update LastSyncCommitSha without advancing LastSyncedAt.
  • Add prune-only protection for bidirectional Spaces (PreserveServerAdditions) and surface pruned paths prominently (per-path + summaries).
  • Add/extend deterministic tests covering no-op horizon behavior, bidirectional prune protection, and forced-prune path reporting.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.

Show a summary per file
File Description
test/MeshWeaver.Graph.Test/ImportConflictPolicyTest.cs Adds unit tests for prune-protection semantics (PreservesFromPruneOf) across modes and force/baseline edge cases.
test/MeshWeaver.GitSync.Test/TwoWaySyncTest.cs Adds regression test ensuring a no-op update records seen commit without moving the conflict horizon, preventing later prune data loss.
test/MeshWeaver.GitSync.Test/BidirectionalPruneProtectionTest.cs New integration tests for bidirectional prune protection, forced-prune auditing, and import-only mirror behavior.
src/MeshWeaver.Graph/StaticRepoImporter.cs Introduces PreserveServerAdditions + PreservesFromPruneOf, counts preserved nodes correctly on skip paths, and logs pruned paths to activity + summary.
src/MeshWeaver.GitSync/GitHubSyncService.cs Applies bidirectional prune-only protection and records seen commits on Skipped outcomes without advancing LastSyncedAt.
src/MeshWeaver.GitSync/GitHubSyncConfig.cs Documents the clarified semantics of LastSyncedAt (conflict horizon) vs LastSyncCommitSha (seen commit marker).
src/MeshWeaver.GitSync/GitHubActivityExtensions.cs Adds user-facing activity lines listing pruned node paths for update/reimport operations.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@github-actions

github-actions Bot commented Aug 7, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 5)

1 549 tests  +4   1 445 ✅ +4   5m 33s ⏱️ + 1m 20s
   11 suites ±0     104 💤 ±0 
   11 files   ±0       0 ❌ ±0 

Results for commit 378d34d. ± Comparison against base commit ecb12d9.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 7, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 4)

1 135 tests  ±0   1 128 ✅ ±0   5m 27s ⏱️ -19s
   11 suites ±0       7 💤 ±0 
   11 files   ±0       0 ❌ ±0 

Results for commit 378d34d. ± Comparison against base commit ecb12d9.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 7, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 0)

367 tests  ±0   363 ✅ ±0   5m 1s ⏱️ +27s
 10 suites ±0     4 💤 ±0 
 10 files   ±0     0 ❌ ±0 

Results for commit 378d34d. ± Comparison against base commit ecb12d9.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 7, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 3)

   10 files  ±0     10 suites  ±0   5m 33s ⏱️ -5s
1 487 tests ±0  1 487 ✅ ±0  0 💤 ±0  0 ❌ ±0 
1 578 runs  ±0  1 578 ✅ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit 378d34d. ± Comparison against base commit ecb12d9.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 7, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 2)

2 019 tests  +6   2 018 ✅ +6   7m 19s ⏱️ +16s
   11 suites ±0       1 💤 ±0 
   11 files   ±0       0 ❌ ±0 

Results for commit 378d34d. ± Comparison against base commit ecb12d9.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 7, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 1)

1 571 tests  ±0   1 382 ✅ ±0   6m 48s ⏱️ +45s
   11 suites ±0     189 💤 ±0 
   11 files   ±0       0 ❌ ±0 

Results for commit 378d34d. ± Comparison against base commit ecb12d9.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 7, 2026 •

Copy link
Copy Markdown
Contributor

Test Results

   64 files  ± 0     64 suites  ±0   35m 44s ⏱️ + 2m 24s
8 128 tests +10  7 823 ✅ +10  305 💤 ±0  0 ❌ ±0 
8 219 runs  +10  7 914 ✅ +10  305 💤 ±0  0 ❌ ±0 

Results for commit 378d34d. ± Comparison against base commit ecb12d9.

♻️ This comment has been updated with latest results.

…l prune protects and names server additions

Fixes #677, fixes #604 — the two remaining routes to GitSync server-side
data loss after #675.

#677: ReimportAtCommit advanced the sync baseline via RecordLastSync (both
LastSyncedAt AND LastSyncCommitSha) whenever Preserved == 0 — including the
fingerprint no-op ('Skipped'). LastSyncedAt is the conflict horizon behind
PreservesServerCopyOf; advancing it without reconciling disarmed the
protection, so a LATER push pruned server-side additions. The fields are
now split by meaning: a Skipped update records only LastSyncCommitSha (the
'seen this commit' marker — up-to-date display and git-diff base stay
correct, a node-free repo commit doesn't leave the space 'behind'); only an
import that actually reconciled records the horizon. Diff-scope and
unchanged-token skips now count a server-newer node as Preserved (the
server-edited-node-whose-file-didn't-change shape).

#604: the prune loop logged paths only on FAILURE and the summary carried a
bare count — a successful prune was untraceable. And prune protection
required TwoWay; a Bidirectional space without TwoWay inherited FullReplace
and mirror-pruned uncommitted server additions. ImportConflictPolicy gains
PreserveServerAdditions (prune-only protection, on for Bidirectional,
reusing #675's kept-from-prune machinery); every prune logs its path and
the activity summary names all pruned paths. ImportOnly mirrors keep
FullReplace semantics — pinned by a guard test.

Fail-without evidence (pre-fix src, new tests): horizon moved on a Skipped
update; Bidirectional server addition pruned; no 'Pruned' activity line —
3 failed / mirror-guard passed. With the fix: GitSync.Test 157/157,
Graph.Test 820/820, Release -warnaserror clean incl. the
Memex.Portal.Monolith umbrella.

Closes #677
Closes #604

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@rbuergi
rbuergi force-pushed the fix/677-604-gitsync-horizon branch from ae43bee to 6087950 Compare August 7, 2026 08:14
@rbuergi
rbuergi merged commit d3d8371 into main Aug 7, 2026
16 checks passed
@rbuergi
rbuergi deleted the fix/677-604-gitsync-horizon branch August 19, 2026 10:32
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.

GitSync: a no-op 'Update to latest' advances the conflict horizon without importing (second route to server-addition loss)

2 participants