Repository: sillsdev/languageforge-lexbox
Pull request #1629: Constraint violation issue
Author: login: hahn-kev
Created: 2025-04-28T07:56:20Z | Merged: 2025-04-29T04:24:39Z

closes #1615
most of the work for this PR was done in https://github.com/sillsdev/harmony/pull/40, however I included some helpful debugging code in the Lcm debugger project.

Linked issue #1615: UNIQUE constraint failed: ComplexFormComponent
Syncing [Saliba-FLEx](https://staging.languagedepot.org/project/sbe-flex?id=813bc17f-a58b-4b47-baf0-ea0d33b5a045) in staging results in: ([trace](https://ui.honeycomb.io/sil-language-forge/environments/staging/result/Ab9TfcBnoMd/trace/EiKkQ6gp9hc?fields[]=s_name&fields[]=s_serviceName&span=1eaf0fa6d9d413c8))

> Microsoft.EntityFrameworkCore.DbUpdateException: Error saving snapshots: An error occurred while saving the entity changes. See the inner exception for details.
> ComplexFormComponent {Id: 00668d14-2992-4a55-834a-747308e85a20} Added FK {ComplexFormEntryId: 5d23f277-ab81-423e-bef7-0176b54d5dcb} FK {ComponentEntryId: eb40aa99-f35a-43b0-aaf8-fe3578f7b422} FK {ComponentSenseId: fb5e815f-de2b-4c7e-be00-ee7bce4653b4} FK {SnapshotId: d9261d90-6529-4e93-a087-a8e88fe5d335}
>  ---> Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while saving the entity changes. See the inner exception for details.
>  ---> Microsoft.Data.Sqlite.SqliteException (0x80004005): SQLite Error 19: 'UNIQUE constraint failed: ComplexFormComponents.ComplexFormEntryId, ComplexFormComponents.ComponentEntryId, ComplexFormComponents.ComponentSenseId'.
>    at Microsoft.Data.Sqlite.SqliteException.ThrowExceptionForRC(Int32 rc, sqlite3 db)
>    at Microsoft.Data.Sqlite.SqliteDataReader.NextResult()
>    at Microsoft.Data.Sqlite.SqliteCommand.ExecuteReader(CommandBehavior behavior)
>    at Microsoft.Data.Sqlite.SqliteCommand.ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken)
>    at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
>    at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
>    at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
>    --- End of inner exception stack trace ---
>    at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
>    at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable\`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
>    at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable\`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
>    at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable\`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
>    at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IList\`1 entriesToSave, CancellationToken cancellationToken)
>    at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(StateManager stateManager, Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
>    at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
>    at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
>    at SIL.Harmony.Db.CrdtRepository.AddSnapshots(IEnumerable\`1 snapshots) in /harmony/src/SIL.Harmony/Db/CrdtRepository.cs:line 270
>    --- End of inner exception stack trace ---
>    at SIL.Harmony.Db.CrdtRepository.AddSnapshots(IEnumerable\`1 snapshots) in /harmony/src/SIL.Harmony/Db/CrdtRepository.cs:line 277
>    at SIL.Harmony.SnapshotWorker.UpdateSnapshots(Commit oldestAddedCommit, Commit[] newCommits) in /harmony/src/SIL.Harmony/SnapshotWorker.cs:line 58
>    at SIL.Harmony.DataModel.UpdateSnapshots(Commit oldestAddedCommit, Commit[] newCommits) in /harmony/src/SIL.Harmony/DataModel.cs:line 214
>    at SIL.Harmony.DataModel.SIL.Harmony.ISyncable.AddRangeFromSync(IEnumerable\`1 commits) in /harmony/src/SIL.Harmony/DataModel.cs:line 159
>    at SIL.Harmony.DataModel.SIL.Harmony.ISyncable.AddRangeFromSync(IEnumerable\`1 commits) in /harmony/src/SIL.Harmony/DataModel.cs:line 161
>    at SIL.Harmony.DataModel.SIL.Harmony.ISyncable.AddRangeFromSync(IEnumerable\`1 commits) in /harmony/src/SIL.Harmony/DataModel.cs:line 172
>    at SIL.Harmony.SyncHelper.SyncWith(ISyncable localModel, ISyncable remoteModel, JsonSerializerOptions serializerOptions) in /harmony/src/SIL.Harmony/SyncHelper.cs:line 42
>    at SIL.Harmony.DataModel.SyncWith(ISyncable remoteModel) in /harmony/src/SIL.Harmony/DataModel.cs:line 343
>    at FwHeadless.Services.CrdtSyncService.SyncHarmonyProject() in /FwHeadless/Services/CrdtSyncService.cs:line 22
>    at FwHeadless.Services.SyncWorker.ExecuteSync(CancellationToken stoppingToken) in /FwHeadless/Services/SyncHostedService.cs:line 156
>    at FwHeadless.Services.SyncHostedService.ExecuteAsync(CancellationToken stoppingToken) in /FwHeadless/Services/SyncHostedService.cs:line 31
Linked issue comments: user: login: hahn-kev
created_at: 2025-04-23T06:42:27Z
body: so I'm not sure what happened here exactly. But it looks like the commits in postgres were wiped out at some point but the sqlite db was not. This might have happened if I was debugging something? I'm not sure. Anyway the result is that there's basically 2 sets of commits making the same project, one in the sqlite file in fw-headless, and one in postgres. When we run a sync then those 2 try to merge and everything blows up. That said it looks like we have a potential sync issue as we could easily end up with 2 users creating the same component and it sounds like that would fail today.
author_association: COLLABORATOR
user: login: hahn-kev
created_at: 2025-04-23T07:49:08Z
body: I did some more investigation (and made some helpful debugging code). I think I understand the problem. There's 2 changes trying to make the same component (according to the constraints, they actually have different guids). One which is already applied gets rolled back because an earlier change is getting synced in, so the snapshots are wiped out, but the projected tables are not, so the component is still in the DB. Then we apply our changes, this queues up creating a new component and the second component should also get deleted, however that may likely happen after the first one is saved which then violates the constraint. I think I can write a failing test for this.
author_association: COLLABORATOR

Diff:
diff --git a/backend/FwLite/LcmDebugger/FakeSyncSource.cs b/backend/FwLite/LcmDebugger/FakeSyncSource.cs
new file mode 100644
index 0000000000..36e51571e6
--- /dev/null
+++ b/backend/FwLite/LcmDebugger/FakeSyncSource.cs
@@ -0,0 +1,76 @@
+﻿using System.Diagnostics.CodeAnalysis;
+using System.Text.Json;
+using SIL.Harmony;
+using SIL.Harmony.Changes;
+using SIL.Harmony.Core;
+
+namespace LcmDebugger;
+
+internal class FakeCommit : Commit
+{
+    [SetsRequiredMembers]
+    public FakeCommit(Guid id, HybridDateTime hybridDateTime) : base(id, "", NullParentHash, hybridDateTime)
+    {
+        HybridDateTime = hybridDateTime;
+        SetParentHash(NullParentHash);
+    }
+}
+
+/// <summary>
+///
+/// </summary>
+/// <param name="commits"></param>
+/// <param name="currentSyncState">when null it will just report to the client that it has the same sync state, that way the client doesn't try to push any changes</param>
+public class FakeSyncSource(Commit[] commits, SyncState? currentSyncState = null) : ISyncable
+{
+    public static FakeSyncSource FromSingleChangeJson(
+        [StringSyntax(StringSyntaxAttribute.Json)] string json,
+        DateTimeOffset commitDate,
+        JsonSerializerOptions? options = null)
+    {
+        var change = JsonSerializer.Deserialize<ChangeEntity<IChange>>(json, options);
+        ArgumentNullException.ThrowIfNull(change);
+        return new FakeSyncSource([new FakeCommit(change.CommitId, new HybridDateTime(commitDate, 0))
+        {
+            ClientId = Guid.NewGuid(),
+            ChangeEntities = [change]
+        }]);
+    }
+
+    public static FakeSyncSource FromJsonFile(string path, JsonSerializerOptions? options = null)
+    {
+        var changes = JsonSerializer.Deserialize<ChangesResult<Commit>>(File.OpenRead(path), options);
+        ArgumentNullException.ThrowIfNull(changes);
+        return new FakeSyncSource(changes.MissingFromClient, changes.ServerSyncState);
+    }
+
+    public Task AddRangeFromSync(IEnumerable<Commit> commits)
+    {
+        return Task.CompletedTask;
+    }
+
+    public Task<SyncState> GetSyncState()
+    {
+        return Task.FromResult(new SyncState([]));
+    }
+
+    public Task<ChangesResult<Commit>> GetChanges(SyncState otherHeads)
+    {
+        return Task.FromResult(new ChangesResult<Commit>(commits, currentSyncState ?? otherHeads));
+    }
+
+    public Task<SyncResults> SyncWith(ISyncable remoteModel)
+    {
+        throw new NotImplementedException();
+    }
+
+    public Task SyncMany(ISyncable[] remotes)
+    {
+        throw new NotImplementedException();
+    }
+
+    public ValueTask<bool> ShouldSync()
+    {
+        return new ValueTask<bool>(true);
+    }
+}
diff --git a/backend/FwLite/LcmDebugger/LcmDebugger.csproj b/backend/FwLite/LcmDebugger/LcmDebugger.csproj
index c1e4f4cba9..8713736394 100644
--- a/backend/FwLite/LcmDebugger/LcmDebugger.csproj
+++ b/backend/FwLite/LcmDebugger/LcmDebugger.csproj
@@ -3,10 +3,12 @@
     <OutputType>Exe</OutputType>
   </PropertyGroup>
   <ItemGroup>
-    <ProjectReference Include="..\..\FwLite\FwDataMiniLcmBridge\FwDataMiniLcmBridge.csproj" />
-      <ProjectReference Include="..\..\FwLite\FwLiteProjectSync\FwLiteProjectSync.csproj" />
+    <ProjectReference Include="..\..\FwLite\FwDataMiniLcmBridge\FwDataMiniLcmBridge.csproj"/>
+    <ProjectReference Include="..\..\FwLite\FwLiteProjectSync\FwLiteProjectSync.csproj"/>
+    <ProjectReference Include="..\FwLiteShared\FwLiteShared.csproj"/>
+    <ProjectReference Include="..\LcmCrdt\LcmCrdt.csproj"/>
   </ItemGroup>
   <ItemGroup>
-    <PackageReference Include="Microsoft.Extensions.Hosting" />
+    <PackageReference Include="Microsoft.Extensions.Hosting"/>
   </ItemGroup>
 </Project>
diff --git a/backend/FwLite/LcmDebugger/Program.cs b/backend/FwLite/LcmDebugger/Program.cs
index 9f5da5186c..b194c0d029 100644
--- a/backend/FwLite/LcmDebugger/Program.cs
+++ b/backend/FwLite/LcmDebugger/Program.cs
@@ -4,14 +4,15 @@
 using FwDataMiniLcmBridge;
 using FwLiteProjectSync;
 using LcmCrdt;
-using LexCore.Exceptions;
+using LcmDebugger;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Options;
+using MiniLcm.Exceptions;
 using Refit;
 using SIL.Harmony;
 using SIL.Harmony.Core;
-using Microsoft.Extensions.Logging;
 
 var builder = Host.CreateApplicationBuilder();
 //slows down import to log all sql.
@@ -22,31 +23,19 @@
 
 using var app = builder.Build();
 
-var fieldWorksProjectList = app.Services.GetRequiredService<FieldWorksProjectList>();
-var fwDataProject = fieldWorksProjectList.GetProject("petit-test-train-flex") ?? throw new InvalidOperationException("Could not find project");
-
-var importService = app.Services.GetRequiredService<MiniLcmImport>();
-await importService.Import(fwDataProject);
 await using var scope = app.Services.CreateAsyncScope();
 
 var crdtProjectsService = app.Services.GetRequiredService<CrdtProjectsService>();
-var crdtProject = crdtProjectsService.GetProject(fwDataProject.Name);
-if (crdtProject is null) throw new NotFoundException("project", "crdt");
-var crdtApi = await crdtProjectsService.OpenProject(crdtProject, scope.ServiceProvider);
-var dataModel = scope.ServiceProvider.GetRequiredService<DataModel>();
-JsonSerializerOptions options = new(JsonSerializerDefaults.Web)
+var crdtApi = await crdtProjectsService.OpenProject(
+    new CrdtProject("test", "sbe-flex - Copy.sqlite"),
+    scope.ServiceProvider);
+var dbContext = scope.ServiceProvider.GetRequiredService<LcmCrdtDbContext>();
+var jsonOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web)
 {
     TypeInfoResolver = scope.ServiceProvider.GetRequiredService<IOptions<CrdtConfig>>().Value
         .MakeLcmCrdtExternalJsonTypeResolver()
 };
-var (commits, _) = await dataModel.GetChanges(new SyncState([]));
-await using var f = File.Create("commits.json");
-await JsonSerializer.SerializeAsync(f, commits, options);
-
-var fwDataFactory = app.Services.GetRequiredService<FwDataFactory>();
-// var miniLcmApi = fwDataFactory.GetFwDataMiniLcmApi(fwDataProject, false);
-// var entries = await miniLcmApi.GetEntries().ToArrayAsync();
-// var entries = miniLcmApi.GetEntries(new(Filter: new() { GridifyFilter = "LexemeForm[sbe]=ta" })).ToArrayAsync();
-// var entry = Utils.GetLexEntry(app.Services, fwDataProject, new Guid("{018d71a9-12c2-4129-be8a-35fe246afda2}"));
-
-fwDataFactory.Dispose();
+await using var transaction = await dbContext.Database.BeginTransactionAsync();
+var dataModel = scope.ServiceProvider.GetRequiredService<DataModel>();
+await dataModel.SyncWith(FakeSyncSource.FromJsonFile("changes.json", jsonOptions));
+await transaction.RollbackAsync();
diff --git a/backend/harmony b/backend/harmony
index e758fbebda..4af7d83b93 160000
--- a/backend/harmony
+++ b/backend/harmony
@@ -1 +1 @@
-Subproject commit e758fbebda4e0f1aba4656ea64b7cda991fde2d2
+Subproject commit 4af7d83b93806a7202e4a108944f454b53e68fe2
diff --git a/deployment/Taskfile.yml b/deployment/Taskfile.yml
index cecc420db5..02d7fcfc72 100644
--- a/deployment/Taskfile.yml
+++ b/deployment/Taskfile.yml
@@ -88,7 +88,17 @@ tasks:
   prod-db-forward:
     cmds:
       - kubectl port-forward service/db 5435:5432 -n languagedepot --context aws-prod
-
+  download-sqlite-file:
+    desc: 'example: `task download-sqlite-file id="<project id here>" code="<project code here>" context="dallas-stage"` the namespace can also be set'
+    requires:
+      vars: [id, code]
+    vars:
+      context: '{{.context| default "docker-desktop"}}'
+      namespace: '{{.namespace| default "languagedepot"}}'
+      pod:
+        sh: kubectl get pods -l app=fw-headless -o jsonpath='{.items[0].metadata.name}' --context {{.context}} --namespace {{.namespace}}
+    cmds:
+      - kubectl cp {{.pod}}:/var/lib/fw-headless/projects/{{.code}}-{{.id}}/crdt.sqlite ./{{.code}}.sqlite --context {{.context}} --namespace {{.namespace}}
 
   diff-staging:
     dir: ./staging


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🧹 Nitpick comments (5)</summary><blockquote>

<details>
<summary>deployment/Taskfile.yml (2)</summary><blockquote>

`98-100`: **Enhance pod selection robustness.**  
Relying on `jsonpath='{.items[0].metadata.name}'` may pick the wrong pod if multiple `fw-headless` pods exist. Consider filtering by project labels or falling back with an error message when no pods match.  


Example:
```diff
-    pod:
-      sh: kubectl get pods -l app=fw-headless -o jsonpath='{.items[0].metadata.name}' --context {{.context}} --namespace {{.namespace}}
+    pod:
+      sh: >
+        kubectl get pods \
+          -l app=fw-headless,project={{.code}}-{{.id}} \
+          -o jsonpath='{.items[0].metadata.name}' \
+          --context {{.context}} \
+          --namespace {{.namespace}} \
+        || (echo "No matching pod found" && exit 1)
```
This ensures you target the correct pod and fail fast if none are found.

---

`101-101`: **Add confirmation or output message.**  
After copying the file, it can be helpful to print a message confirming the local path and size, to aid in debugging.  


You could extend the `cmds` list:
```yaml
    cmds:
      - kubectl cp {{.pod}}:/var/lib/fw-headless/projects/{{.code}}-{{.id}}/crdt.sqlite ./{{.code}}.sqlite --context {{.context}} --namespace {{.namespace}}
      - echo "Downloaded crdt.sqlite to ./{{.code}}.sqlite (size: $(stat -c%s ./{{.code}}.sqlite) bytes)"
```

</blockquote></details>
<details>
<summary>backend/FwLite/LcmDebugger/Program.cs (1)</summary><blockquote>

`29-31`: **Consider making project parameters configurable**

The hardcoded project name and database file path work for debugging purposes, but consider making these configurable via command-line arguments or configuration file for better flexibility.

```diff
-var crdtApi = await crdtProjectsService.OpenProject(
-    new CrdtProject("test", "sbe-flex - Copy.sqlite"),
-    scope.ServiceProvider);
+// Allow configuration via args or config file
+var projectName = args.Length > 0 ? args[0] : "test";
+var dbPath = args.Length > 1 ? args[1] : "sbe-flex - Copy.sqlite";
+var crdtApi = await crdtProjectsService.OpenProject(
+    new CrdtProject(projectName, dbPath),
+    scope.ServiceProvider);
```

</blockquote></details>
<details>
<summary>backend/FwLite/LcmDebugger/FakeSyncSource.cs (2)</summary><blockquote>

`19-23`: **Complete the class documentation**

The XML documentation for FakeSyncSource has empty summary tags. Consider completing the documentation to explain the purpose and usage of this class.

```diff
 /// <summary>
-///
+/// A mock implementation of ISyncable for testing and debugging purposes.
+/// Provides simulated sync data from predefined commits without requiring
+/// actual network communication or persistence.
 /// </summary>
 /// <param name="commits"></param>
 /// <param name="currentSyncState">when null it will just report to the client that it has the same sync state, that way the client doesn't try to push any changes</param>
```

---

`62-70`: **Document unimplemented interface methods**

SyncWith and SyncMany methods throw NotImplementedException without explanation. Consider adding documentation comments explaining why these methods are not implemented and under what circumstances they might be called.

```diff
+    /// <summary>
+    /// Synchronizes with a remote model.
+    /// </summary>
+    /// <remarks>
+    /// Not implemented in this mock implementation as it's not needed for the current debugging scenarios.
+    /// </remarks>
     public Task<SyncResults> SyncWith(ISyncable remoteModel)
     {
         throw new NotImplementedException();
     }

+    /// <summary>
+    /// Synchronizes with multiple remote models.
+    /// </summary>
+    /// <remarks>
+    /// Not implemented in this mock implementation as it's not needed for the current debugging scenarios.
+    /// </remarks>
     public Task SyncMany(ISyncable[] remotes)
     {
         throw new NotImplementedException();
     }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 07956c27594e66ad9ac8e5c7ed241c1143e35c7d and 643b70ffac223c9430afa6ab5c753acc0eb97cf2.

</details>

<details>
<summary>📒 Files selected for processing (5)</summary>

* `backend/FwLite/LcmDebugger/FakeSyncSource.cs` (1 hunks)
* `backend/FwLite/LcmDebugger/LcmDebugger.csproj` (1 hunks)
* `backend/FwLite/LcmDebugger/Program.cs` (2 hunks)
* `backend/harmony` (1 hunks)
* `deployment/Taskfile.yml` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>backend/FwLite/LcmDebugger/Program.cs (1)</summary><blockquote>

<details>
<summary>backend/FwLite/LcmDebugger/FakeSyncSource.cs (3)</summary>

* `FakeSyncSource` (24-76)
* `FakeSyncSource` (26-38)
* `FakeSyncSource` (40-45)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (3)</summary>

* GitHub Check: Build API / publish-api
* GitHub Check: Build FW Lite and run tests
* GitHub Check: Analyze (csharp)

</details>

<details>
<summary>🔇 Additional comments (8)</summary><blockquote>

<details>
<summary>backend/FwLite/LcmDebugger/LcmDebugger.csproj (1)</summary>

`6-9`: **Project references updated correctly**

The addition of references to FwLiteShared and LcmCrdt projects is appropriate for supporting the new debugging functionality. The formatting cleanup on existing references also improves consistency.

</details>
<details>
<summary>backend/FwLite/LcmDebugger/Program.cs (3)</summary>

`7-12`: **Appropriate namespaces added**

The addition of LcmDebugger, Microsoft.Extensions.Logging, and MiniLcm.Exceptions namespaces properly supports the new debugging functionality and exception handling.

---

`33-37`: **Variable rename improves clarity**

Renaming from `options` to `jsonOptions` better describes the variable's purpose and improves code readability.

---

`38-41`: **Transaction-based debugging approach is effective**

Using a transaction that gets rolled back is an excellent approach for debugging synchronization issues without persisting changes to the database. This allows for safe testing of constraint violations.

</details>
<details>
<summary>backend/FwLite/LcmDebugger/FakeSyncSource.cs (4)</summary>

`9-17`: **Well-implemented FakeCommit class**

The FakeCommit class correctly extends Commit and provides appropriate test data construction. The use of SetsRequiredMembers attribute shows good understanding of C# features.

---

`26-38`: **Good factory method implementation**

The FromSingleChangeJson factory method is well-implemented with proper null checking and parameter documentation. The use of StringSyntax attribute is a good practice for providing IDE hints.

---

`40-45`: **Effective file loading implementation**

The FromJsonFile factory method correctly deserializes from a file and handles potential null results. This provides a convenient way to load test data from external files.

---

`72-75`: **Appropriate ShouldSync implementation**

Always returning true for ShouldSync is appropriate for a testing implementation, ensuring that synchronization is always attempted when requested.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-28T08:02:03Z
user: login: myieye
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-28T09:10:52Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-28T09:11:21Z
user: login: myieye
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-28T09:45:03Z

Review comments:
diff_hunk: @@ -88,7 +88,17 @@ tasks:
   prod-db-forward:
     cmds:
       - kubectl port-forward service/db 5435:5432 -n languagedepot --context aws-prod
-
+  download-sqlite-file:
+    desc: 'example: `task download-sqlite-file id="<project id here>" code="<project code here>" context="dallas-stage"` the namespace can also be set'
+    requires:
+      vars: [id, code]
+    vars:
+      context: '{{.context| default "docker-desktop"}}'
+      namespace: '{{.namespace| default "languagedept"}}'
path: deployment/Taskfile.yml
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix default namespace typo.**  
The default namespace is set to `"languagedept"`, but all other tasks use `"languagedepot"`. This typo will cause the command to target a non-existent namespace.  


Apply this diff:
```diff
-      namespace: '{{.namespace| default "languagedept"}}'
+      namespace: '{{.namespace| default "languagedepot"}}'
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
      context: '{{.context| default "docker-desktop"}}'
      namespace: '{{.namespace| default "languagedepot"}}'
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-28T08:02:02Z
author_association: NONE
diff_hunk: @@ -1 +1 @@
-Subproject commit e758fbebda4e0f1aba4656ea64b7cda991fde2d2
+Subproject commit 4af7d83b93806a7202e4a108944f454b53e68fe2
path: backend/harmony
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>❓ Verification inconclusive</summary>

**Verify submodule commit reference**  
Ensure that the updated commit `4af7d83b93806a7202e4a108944f454b53e68fe2` exists in the `backend/harmony` submodule’s remote and is the intended target.  


Run this script to confirm the commit is reachable:

---

🏁 Script executed:

```shell
#!/bin/bash
# Verify that the new submodule SHA exists in its remote repository
url=$(git config --file .gitmodules --get submodule.backend/harmony.url)
git ls-remote "$url" 4af7d83b93806a7202e4a108944f454b53e68fe2
```

Length of output: 211

---



</details>

**Submodule Commit SHA Not Found in Remote**  
The specified commit `4af7d83b93806a7202e4a108944f454b53e68fe2` does not exist in the remote repository at `https://github.com/sillsdev/harmony.git`.  
Please ensure that this commit is pushed to the remote or update the submodule to a valid SHA before merging.

Suggested verification command:  
```bash
git ls-remote https://github.com/sillsdev/harmony.git 4af7d83b93806a7202e4a108944f454b53e68fe2
```

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-04-28T08:02:02Z
author_association: NONE
diff_hunk: @@ -1 +1 @@
-Subproject commit e758fbebda4e0f1aba4656ea64b7cda991fde2d2
+Subproject commit 4af7d83b93806a7202e4a108944f454b53e68fe2
path: backend/harmony
user: login: myieye
body: Yes, it does exist: https://github.com/sillsdev/harmony/commit/4af7d83b93806a7202e4a108944f454b53e68fe2

I don't think you can use git ls-remote to find any commit like that.
created_at: 2025-04-28T09:10:51Z
author_association: COLLABORATOR
diff_hunk: @@ -1 +1 @@
-Subproject commit e758fbebda4e0f1aba4656ea64b7cda991fde2d2
+Subproject commit 4af7d83b93806a7202e4a108944f454b53e68fe2
path: backend/harmony
user: login: coderabbitai[bot]
body: @myieye You're absolutely right on both counts! The commit does indeed exist as confirmed by the GitHub URL you provided: https://github.com/sillsdev/harmony/commit/4af7d83b93806a7202e4a108944f454b53e68fe2

The verification method used (`git ls-remote`) has limitations - it primarily returns references (branches, tags) and their associated SHAs, not arbitrary commit SHAs unless they're specifically pointed to by a reference. That's why the verification incorrectly reported the commit wasn't found.

A more reliable verification method would be to use:
```bash
git fetch --depth=1 origin 4af7d83b93806a7202e4a108944f454b53e68fe2
```

Thank you for the correction!

---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: myieye
PR: sillsdev/languageforge-lexbox#1629
File: backend/harmony:1-1
Timestamp: 2025-04-28T09:11:20.061Z
Learning: When verifying submodule commit references in git, `git ls-remote` may not find valid commits if they aren't referenced by branches or tags. Better alternatives include checking the commit directly on GitHub or using `git fetch --depth=1 origin <commit-sha>`.
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>




<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@myieye You're absolutely right on both counts! The commit does indeed exist as confirmed by the GitHub URL you provided: https://github.com/sillsdev/harmony/commit/4af7d83b93806a7202e4a108944f454b53e68fe2

The verification method used (`git ls-remote`) has limitations - it primarily returns references (branches, tags) and their associated SHAs, not arbitrary commit SHAs unless they're specifically pointed to by a reference. That's why the verification incorrectly reported the commit wasn't found.

A more reliable verification method would be to use:
```bash
git fetch --depth=1 origin 4af7d83b93806a7202e4a108944f454b53e68fe2
```

Thank you for the correction!

---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: myieye
PR: sillsdev/languageforge-lexbox#1629
File: backend/harmony:1-1
Timestamp: 2025-04-28T09:11:20.061Z
Learning: When verifying submodule commit references in git, `git ls-remote` may not find valid commits if they aren't referenced by branches or tags. Better alternatives include checking the commit directly on GitHub or using `git fetch --depth=1 origin <commit-sha>`.
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>

</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-04-28T09:11:21Z
author_association: NONE

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-04-28T07:56:27Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: skip review by coderabbit.ai -->

> [!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.

<!-- end of auto-generated comment: skip review by coderabbit.ai -->
<!-- walkthrough_start -->

<details>
<summary>📝 Walkthrough</summary>

## Walkthrough

This update introduces a new mock synchronization source for testing purposes in the backend, specifically within the LcmDebugger module. The `FakeSyncSource` class and its supporting `FakeCommit` class allow for simulated commit retrieval and sync state reporting, facilitating debugging and testing without real synchronization. The project file is updated to reference additional shared projects. The main program logic is refactored to use the new fake sync source and simulate synchronization within a transaction that is rolled back. A new deployment task is added to facilitate downloading SQLite files from Kubernetes pods. The harmony submodule is updated to a new commit.

## Changes

| Files/Paths                                                        | Change Summary                                                                                                                                                                                                                                       |
|--------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| backend/FwLite/LcmDebugger/FakeSyncSource.cs                       | Added new `FakeCommit` (internal) and `FakeSyncSource` (public, implements `ISyncable`) classes to provide a mock sync source for testing, with methods for deserializing commits from JSON and simulating sync operations.                         |
| backend/FwLite/LcmDebugger/Program.cs                              | Refactored main logic to remove FieldWorks project import/export, directly open a CRDT project, and simulate synchronization using the new `FakeSyncSource` within a transaction that is rolled back.                                               |
| backend/FwLite/LcmDebugger/LcmDebugger.csproj                      | Added project references to `FwLiteShared.csproj` and `LcmCrdt.csproj`; reformatted existing references and package references.                                                                              |
| backend/harmony                                                    | Updated submodule commit reference from `e758fbeb` to `4af7d83b`.                                                                                                                                                                                  |
| deployment/Taskfile.yml                                            | Added `download-sqlite-file` task to copy a SQLite file from a Kubernetes pod to the local machine, with customizable variables and usage description.                                                       |

## Suggested reviewers

- myieye

## Poem

> 🐇  
> In the debugger’s warren, new code appears,  
> Fake sync hops in, allaying our fears.  
> Commits from JSON, a playful delight,  
> Transactions roll back, all tests run right.  
> With tasks for pods and harmony’s tune,  
> This rabbit’s code garden is growing soon!  
> 🥕

</details>

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- tips_start -->

---

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.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=sillsdev/languageforge-lexbox&utm_content=1629):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-28T08:00:26Z
body: ## C# Unit Tests
116 tests  ±0   116 :white_check_mark: ±0   10s :stopwatch: ±0s
 17 suites ±0     0 :zzz: ±0 
  1 files   ±0     0 :x: ±0 

Results for commit 643b70ff. ± Comparison against base commit 07956c27.

[test-results]:data:application/gzip;base64,H4sIAJc1D2gC/13MTQ6DIBCG4asY1l0gKMRexgxTJiFVafhZNb17EduqXT7fZN4nIzfZyK5Ne2lYzC5t0EW3HCA5v6zmxeWU6rFVX40xI/5Pd/co0+9jJHDTabAh+PBZQl725opzclv2YvUhWH3soZ9nlwqY6qTRnAhQCIlDJzkQKDA96l4CIrdm0EiCvd60d82VBQEAAA==

author_association: CONTRIBUTOR