Skip to content

Only regenerate snapshot if Send/Receive works#2069

Merged
myieye merged 25 commits intodevelopfrom
2052-only-regenerate-snapshot-if-send-receive-works
Feb 11, 2026
Merged

Only regenerate snapshot if Send/Receive works#2069
myieye merged 25 commits intodevelopfrom
2052-only-regenerate-snapshot-if-send-receive-works

Conversation

@myieye
Copy link
Collaborator

@myieye myieye commented Oct 22, 2025

Resolves #2052

This PR pulls snapshot regeneration/updating out of the Sync method, so that it is only done after a successful Send/Receive.

Additionally, all the other snapshot code was pulled out (except for a basic verification that Sync vs Import are being called correctly), which meant a large refactor, because snapshots are now managed externally to the Sync method: they need to be manually fetched, explicitly passed and manually regenerated. This makes things more explicit, definitely a bit more verbose, but, in my opinion easier to follow and there are now certainly enough tests in place to ensure that our sync code does this correctly.

@github-actions github-actions bot added 💻 FW Lite issues related to the fw lite application, not miniLcm or crdt related 📦 Lexbox issues related to any server side code, fw-headless included labels Oct 22, 2025
@coderabbitai
Copy link

coderabbitai bot commented Oct 22, 2025

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR refactors dependency injection to use interface abstractions for four services (ISyncJobStatusService, ISendReceiveService, IProjectLookupService, IProjectMetadataService), introduces a new ProjectSnapshotService for snapshot management, and enhances the sync workflow to detect and handle Send/Receive rollbacks by blocking projects from future synchronization.

Changes

Cohort / File(s) Summary
DI Interface Extraction
backend/FwHeadless/FwHeadlessKernel.cs, backend/FwHeadless/Services/SendReceiveService.cs, backend/FwHeadless/Services/ProjectLookupService.cs, backend/FwHeadless/Services/ProjectMetadataService.cs, backend/FwHeadless/Services/ProjectSyncStatusService.cs
Services now implement newly-created interfaces (ISendReceiveService, IProjectLookupService, IProjectMetadataService, ISyncJobStatusService) and are registered via interfaces in the DI container.
Interface Definitions
backend/FwHeadless/Services/ISendReceiveService.cs, backend/FwHeadless/Services/IProjectLookupService.cs, backend/FwHeadless/Services/IProjectMetadataService.cs, backend/FwHeadless/Services/ISyncJobStatusService.cs
Four new public interfaces define contracts for service methods, enabling loose coupling and testability.
DI Consumer Updates
backend/FwHeadless/Services/ProjectContextFromIdService.cs, backend/FwHeadless/Services/ProjectDeletionService.cs, backend/FwHeadless/Media/MediaFileService.cs
Constructor parameters updated to accept interfaces instead of concrete types.
Route Handler Signatures
backend/FwHeadless/Routes/MergeRoutes.cs
All endpoint handlers refactored to accept interface-based dependencies (IProjectLookupService, IProjectMetadataService, ISendReceiveService, ISyncJobStatusService) instead of concrete services.
Rollback Detection & Blocking
backend/FwHeadless/Services/SendReceiveHelpers.cs, backend/FwHeadless/Services/SyncHostedService.cs
SendReceiveHelpers enhanced to detect rollback scenarios via RollbackDetected property; SyncHostedService now blocks projects when rollback occurs to prevent snapshot corruption.
Project Metadata Service Refactoring
backend/FwHeadless/Services/ProjectMetadataService.cs
Method renamed GetSyncBlockInfoAsync → GetSyncBlockedInfoAsync; type renamed SyncBlockInfo → SyncBlockedInfo.
New Snapshot Service
backend/FwLite/FwLiteProjectSync/ProjectSnapshotService.cs, backend/FwLite/FwLiteProjectSync/FwLiteProjectSyncKernel.cs
New service manages snapshot persistence, retrieval, and regeneration; reduces responsibilities from CrdtFwdataProjectSyncService.
Sync Flow & Orchestration
backend/FwHeadless/Services/SyncHostedService.cs, backend/FwLite/FwLiteProjectSync/CrdtFwdataProjectSyncService.cs, backend/FwLite/FwLiteProjectSync/Program.cs
Sync workflow updated to use snapshots for Import vs Sync decision; snapshot regeneration deferred until after successful changes; Sync/Import methods now accept ProjectSnapshot parameter.
Method Virtualization
backend/FwHeadless/Services/CrdtSyncService.cs, backend/FwLite/LcmCrdt/CrdtProjectsService.cs, backend/FwLite/LcmCrdt/RemoteSync/CrdtHttpSyncService.cs, backend/FwHeadless/Media/MediaFileService.cs
Public methods made virtual (SyncHarmonyProject, CreateProject, TestAuth, SyncMediaFiles) to enable test overrides and subclassing.
Sync Result Enhancements
backend/LexCore/Sync/SyncJobResult.cs
Added SuccessHarmonyOnly enum value to track Harmony-only synchronization outcomes.
Comprehensive Test Updates
backend/FwLite/FwLiteProjectSync.Tests/*, backend/Testing/FwHeadless/*
Tests refactored to use Import + snapshot-aware Sync workflow; new SyncWorkerTestHarness and SyncWorkerTests added for thorough worker orchestration testing; existing tests updated to align with snapshot parameters.
Debugging & Utilities
backend/FwLite/LcmDebugger/Utils.cs, backend/FwLite/FwLiteProjectSync.Tests/TestingKernel.cs
Utilities updated to use Import/Sync snapshot flow; logging configuration adjusted.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • PR #2123: Adds project metadata blocking and SyncJobStatus values; overlaps with blocking/metadata service refactoring in this PR.
  • PR #1913: Introduces ProjectSnapshot and snapshot-centric sync workflows; closely related to the snapshot management and Import vs Sync decision logic in this PR.
  • PR #1924: Adds SendReceiveException and rollback detection in SendReceiveHelpers; directly related to rollback handling introduced here to fix issue #2052.

Suggested reviewers

  • rmunn

Poem

🐰 Snapshots saved and rollbacks caught,
Interfaces wired just as we thought,
Blocks prevent the CRDT blight,
When Send/Receive goes not quite right!
Fresh hops for safer sync in sight! 🌱

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.20% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and accurately describes the primary change: snapshot regeneration is now conditional on successful Send/Receive operations.
Description check ✅ Passed The description directly relates to the changeset and explains the core objective: moving snapshot regeneration out of the Sync method so it only occurs after successful Send/Receive.
Linked Issues check ✅ Passed The PR fully addresses the linked issue #2052 by ensuring snapshots are only regenerated after successful Send/Receive, preventing snapshots from falsely reflecting rolled-back changes.
Out of Scope Changes check ✅ Passed All changes directly support the objective: interface extraction for dependency injection, snapshot externalization, virtual method additions for extensibility, and comprehensive test additions to validate the new behavior.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 2052-only-regenerate-snapshot-if-send-receive-works

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Contributor

github-actions bot commented Oct 22, 2025

C# Unit Tests

160 tests   160 ✅  18s ⏱️
 23 suites    0 💤
  1 files      0 ❌

Results for commit b04fc67.

♻️ This comment has been updated with latest results.

@argos-ci
Copy link

argos-ci bot commented Oct 22, 2025

The latest updates on your projects. Learn more about Argos notifications ↗︎

Build Status Details Updated (UTC)
default (Inspect) ✅ No changes detected - Feb 11, 2026, 10:25 AM

@github-actions
Copy link
Contributor

github-actions bot commented Oct 22, 2025

UI unit Tests

  1 files  ±0   50 suites  ±0   23s ⏱️ -1s
138 tests ±0  138 ✅ ±0  0 💤 ±0  0 ❌ ±0 
203 runs  ±0  203 ✅ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit b04fc67. ± Comparison against base commit a43a321.

♻️ This comment has been updated with latest results.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 19cc689 and 1437481.

📒 Files selected for processing (4)
  • backend/FwHeadless/Services/SyncHostedService.cs (1 hunks)
  • backend/FwLite/FwLiteProjectSync.Tests/CrdtRepairTests.cs (2 hunks)
  • backend/FwLite/FwLiteProjectSync.Tests/Sena3SyncTests.cs (1 hunks)
  • backend/FwLite/FwLiteProjectSync/CrdtFwdataProjectSyncService.cs (0 hunks)
💤 Files with no reviewable changes (1)
  • backend/FwLite/FwLiteProjectSync/CrdtFwdataProjectSyncService.cs
🧰 Additional context used
🧬 Code graph analysis (2)
backend/FwHeadless/Services/SyncHostedService.cs (1)
backend/LexCore/Entities/Project.cs (1)
  • Project (9-75)
backend/FwLite/FwLiteProjectSync.Tests/CrdtRepairTests.cs (1)
backend/FwLite/FwLiteShared/Sync/SyncService.cs (1)
  • SyncService (23-240)
🪛 GitHub Actions: Develop FwHeadless CI/CD
backend/FwHeadless/Services/SyncHostedService.cs

[error] 223-223: CS1002: ; expected. Command: dotnet build backend/FwHeadless/FwHeadless.csproj

🪛 GitHub Check: Build FwHeadless / publish-fw-headless
backend/FwHeadless/Services/SyncHostedService.cs

[failure] 223-223:
} expected


[failure] 223-223:
; expected


[failure] 223-223:
} expected


[failure] 223-223:
; expected

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Build API / publish-api
  • GitHub Check: Build FW Lite and run tests
  • GitHub Check: frontend
  • GitHub Check: frontend-component-unit-tests
  • GitHub Check: Analyze (csharp)
🔇 Additional comments (5)
backend/FwLite/FwLiteProjectSync.Tests/Sena3SyncTests.cs (1)

228-228: Test adapted to new explicit snapshot regeneration pattern.

The explicit call to RegenerateProjectSnapshot after Sync is the expected pattern following the removal of automatic snapshot regeneration from the sync service.

backend/FwLite/FwLiteProjectSync.Tests/CrdtRepairTests.cs (2)

93-93: Test adapted to new explicit snapshot regeneration pattern.

The explicit call to RegenerateProjectSnapshot after Sync is the expected pattern following the removal of automatic snapshot regeneration from the sync service.


160-160: Test adapted to new explicit snapshot regeneration pattern.

The explicit call to RegenerateProjectSnapshot after Sync is the expected pattern following the removal of automatic snapshot regeneration from the sync service.

backend/FwHeadless/Services/SyncHostedService.cs (2)

210-224: Conditional snapshot regeneration correctly implements PR objective.

The logic correctly ensures snapshots are only regenerated when:

  1. CRDT changes exist (result.CrdtChanges > 0), AND
  2. Send/Receive either succeeded or wasn't needed (no early return at line 203)

If Send/Receive fails after CRDT sync (line 203), the early return prevents snapshot regeneration, which is correct since the changes might have been rolled back.


226-226: Verify dual Harmony sync pattern is intentional.

The code now performs Harmony sync twice:

  1. Conditionally at line 176 (if HasSyncedSuccessfully)
  2. Unconditionally at line 226 (after snapshot regeneration)

This appears intentional to ensure Harmony is synced both before and after the CRDT/FW sync operations, but you may want to verify this pattern is necessary and doesn't cause redundant work.

@myieye myieye marked this pull request as draft October 24, 2025 07:39
@myieye myieye force-pushed the 2052-only-regenerate-snapshot-if-send-receive-works branch 2 times, most recently from 3e9c79d to d14de81 Compare January 29, 2026 14:33
@myieye myieye force-pushed the 2052-only-regenerate-snapshot-if-send-receive-works branch from 3e4c29b to 3007145 Compare February 3, 2026 17:02
@myieye myieye marked this pull request as ready for review February 4, 2026 08:12
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In `@backend/FwHeadless/Services/ProjectMetadataService.cs`:
- Around line 10-22: BlockFromSyncAsync currently sets
metadata.SyncBlocked.Reason to reason ?? "Manual block" but logs the original
nullable reason, which can print "null"; fix by computing a single
resolvedReason (e.g., var resolvedReason = reason ?? "Manual block") and use
that resolvedReason both when assigning metadata.SyncBlocked.Reason inside the
_store.UpdateAsync lambda and when calling logger.LogWarning so the log shows
the actual stored reason; reference BlockFromSyncAsync, _store.UpdateAsync,
metadata.SyncBlocked, SyncBlockedInfo, and logger.LogWarning.

In `@backend/FwLite/FwLiteProjectSync/CrdtFwdataProjectSyncService.cs`:
- Around line 57-88: The callers of Sync()/Import() must regenerate the project
snapshot after a successful non-dry-run operation; update the call sites in
Program.cs and Utils.cs to, after the awaited Sync()/Import() returns
successfully and dryRun is false, call and await
projectSnapshotService.RegenerateProjectSnapshot(crdtApi, fwdataApi.Project,
keepBackup: false) (following the pattern in SyncHostedService.cs). Ensure you
pass the same crdtApi and fwdataApi.Project objects used for the sync/import,
await the regeneration, and only skip this step when dryRun is true.

In `@backend/FwLite/FwLiteProjectSync/Program.cs`:
- Around line 68-75: The code handles Import vs Sync but never regenerates the
ProjectSnapshot afterwards; update the flow so that once syncService.Import or
syncService.Sync completes successfully you regenerate and persist the snapshot
from the CRDT (not from FwData). After the existing call to
logger.LogInformation, call the ProjectSnapshotService method that creates/saves
a snapshot from the CRDT (use ProjectSnapshotService with the crdtApi/its
Project or CRDT root) so the new snapshot replaces the old one and future syncs
use the CRDT-derived snapshot.

In `@backend/FwLite/FwLiteProjectSync/ProjectSnapshotService.cs`:
- Around line 12-18: GetProjectSnapshot currently deserializes with
crdtConfig.Value.JsonSerializerOptions which filters out [MiniLcmInternal]
fields and causes data loss; change GetProjectSnapshot to deserialize using the
same serializer options used in SaveProjectSnapshot (the options that preserve
[MiniLcmInternal] fields) so round-trip preserves Order/MaybeId/etc. Locate
GetProjectSnapshot and replace the JsonSerializer.DeserializeAsync call to pass
the matching preserved-options instance (the same variable or constant used by
SaveProjectSnapshot) and ensure SnapshotPath and file handling remain unchanged.

In `@backend/FwLite/LcmDebugger/Utils.cs`:
- Around line 77-88: SyncFwHeadlessProject currently chooses Import vs Sync
(CrdtFwdataProjectSyncService.Import / Sync) based on
snapshotService.GetProjectSnapshot but never updates the stored snapshot after a
successful non-dry-run run; update the code so that when dryRun is false and the
Import/Sync returns success you regenerate/persist the new snapshot via the
ProjectSnapshotService (e.g., call the service's method that creates/updates the
project snapshot for fwDataMiniLcmApi.Project — use the appropriate
ProjectSnapshotService method such as
UpdateProjectSnapshot/CreateProjectSnapshot/SaveProjectSnapshot) so subsequent
runs see the fresh snapshot.
🧹 Nitpick comments (4)
backend/Testing/FwHeadless/ProjectMetadataServiceTests.cs (1)

14-17: Consider disposing the LoggerFactory.

The LoggerFactory created in the constructor implements IDisposable but is never disposed. While this is unlikely to cause issues in test scenarios, it's good practice to dispose it.

♻️ Proposed fix
-public class ProjectMetadataServiceTests : IAsyncLifetime
+public class ProjectMetadataServiceTests : IAsyncLifetime, IDisposable
 {
-    private readonly ILogger<ProjectMetadataService> _logger;
+    private readonly LoggerFactory _loggerFactory;
+    private readonly ILogger<ProjectMetadataService> _logger;
     private FwHeadlessConfig _config = null!;
     private string _tempDir = null!;

     public ProjectMetadataServiceTests()
     {
-        _logger = new LoggerFactory().CreateLogger<ProjectMetadataService>();
+        _loggerFactory = new LoggerFactory();
+        _logger = _loggerFactory.CreateLogger<ProjectMetadataService>();
     }
+
+    public void Dispose()
+    {
+        _loggerFactory.Dispose();
+    }
backend/FwLite/FwLiteProjectSync/Program.cs (1)

20-37: Placeholder commands - consider removing or documenting intent.

The before-sync and after-sync commands only print the arguments and don't perform any actual operations. If these are intentional hooks for future use, consider adding a comment explaining their purpose. Otherwise, consider removing them to avoid confusion.

backend/FwLite/FwLiteProjectSync.Tests/ProjectSnapshotServiceTests.cs (1)

134-151: TODO tracking: CRDT-only enforcement.
Please track/resolve this TODO before release so the invariant doesn’t get lost.

If you want, I can draft a small follow-up task or propose an API change to enforce this invariant.

backend/FwLite/FwLiteProjectSync/ProjectSnapshotService.cs (1)

36-54: Consider atomic snapshot writes to avoid partial files.
Directly writing to the target path risks truncated or partially written snapshots if the process crashes mid‑write; that can later break sync flows. Writing to a temp file and replacing reduces this risk.

✅ Safer write pattern
     internal static async Task SaveProjectSnapshot(FwDataProject project, ProjectSnapshot projectSnapshot, bool keepBackup = false)
     {
         var snapshotPath = SnapshotPath(project);
@@
-        await using var file = File.Create(snapshotPath);
-        //not using our serialization options because we don't want to exclude MiniLcmInternal
-        await JsonSerializer.SerializeAsync(file, projectSnapshot);
+        var tempPath = Path.Combine(Path.GetDirectoryName(snapshotPath)!, Path.GetRandomFileName());
+        await using (var file = File.Create(tempPath))
+        {
+            //not using our serialization options because we don't want to exclude MiniLcmInternal
+            await JsonSerializer.SerializeAsync(file, projectSnapshot);
+        }
+        File.Move(tempPath, snapshotPath, overwrite: true);
     }

@rmunn
Copy link
Contributor

rmunn commented Feb 11, 2026

Some files (e.g. MediaFileService.cs) have been converted from LF line endings to CRLF line endings in this PR, resulting in a diff that incorrectly shows every line changed (until you click on the gear icon in GitHub and choose "Hide whitespace". Is that the result of editor misconfiguration, or did an AI touch that file and cause the CRLF to get added?

@myieye myieye force-pushed the 2052-only-regenerate-snapshot-if-send-receive-works branch from 6b07241 to b04fc67 Compare February 11, 2026 10:23
@myieye myieye merged commit 624ea9c into develop Feb 11, 2026
24 checks passed
@myieye myieye deleted the 2052-only-regenerate-snapshot-if-send-receive-works branch February 11, 2026 10:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💻 FW Lite issues related to the fw lite application, not miniLcm or crdt related 📦 Lexbox issues related to any server side code, fw-headless included

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Concluding Send/Receive Failed, rolled-back and lost all crdt changes

2 participants