Skip to content

Commit 3d72253

Browse files
myieyeclaude
andcommitted
Use named GUIDs for predefined-data seed commits
Each AddPredefined* now derives its seed commit-id per-project via UUIDv5 (Uuid.NewNameBased), so the LexBox CrdtCommits PK no longer collides across projects — the seed row gets attributed to its own project instead of whichever client pushed first. AddPredefined* signatures now take ProjectData instead of (clientId, projectId). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent b1ab821 commit 3d72253

5 files changed

Lines changed: 46 additions & 28 deletions

File tree

backend/FwLite/LcmCrdt.Tests/MiniLcmApiFixture.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ await CrdtProjectsService.InitProjectDb(_crdtDbContext,
7777
projectData);
7878
await currentProjectService.RefreshProjectData();
7979
// CreateProject would also seed morph types — so we need to do it manually here
80-
await PreDefinedData.AddPredefinedMorphTypes(_services.ServiceProvider.GetRequiredService<DataModel>(), projectData.ClientId);
80+
await PreDefinedData.AddPredefinedMorphTypes(_services.ServiceProvider.GetRequiredService<DataModel>(), projectData);
8181
if (_seedWs)
8282
{
8383
await Api.CreateWritingSystem(new WritingSystem()

backend/FwLite/LcmCrdt/CrdtProjectsService.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,9 @@ public virtual async Task<CrdtProject> CreateProject(CreateProjectRequest reques
169169
// Morph types are predefined system data that must always exist — seed them
170170
// unconditionally so they're available before AfterCreate (e.g. import) runs.
171171
var dataModel = serviceScope.ServiceProvider.GetRequiredService<DataModel>();
172-
await PreDefinedData.AddPredefinedMorphTypes(dataModel, projectData.ClientId);
172+
await PreDefinedData.AddPredefinedMorphTypes(dataModel, projectData);
173173
if (request.SeedNewProjectData)
174-
await SeedSystemData(dataModel, projectData.ClientId);
174+
await SeedSystemData(dataModel, projectData);
175175
await (request.AfterCreate?.Invoke(serviceScope.ServiceProvider, crdtProject) ?? Task.CompletedTask);
176176
}
177177
catch (Exception e)
@@ -243,13 +243,13 @@ internal static async Task InitProjectDb(LcmCrdtDbContext db, ProjectData data)
243243
await db.SaveChangesAsync();
244244
}
245245

246-
internal static async Task SeedSystemData(DataModel dataModel, Guid clientId)
246+
internal static async Task SeedSystemData(DataModel dataModel, ProjectData projectData)
247247
{
248248
// Note: AddPredefinedMorphTypes is seeded unconditionally in CreateProject, not here.
249-
await PreDefinedData.AddPredefinedComplexFormTypes(dataModel, clientId);
250-
await PreDefinedData.AddPredefinedPartsOfSpeech(dataModel, clientId);
251-
await PreDefinedData.AddPredefinedSemanticDomains(dataModel, clientId);
252-
await PreDefinedData.AddPredefinedCustomViews(dataModel, clientId);
249+
await PreDefinedData.AddPredefinedComplexFormTypes(dataModel, projectData);
250+
await PreDefinedData.AddPredefinedPartsOfSpeech(dataModel, projectData);
251+
await PreDefinedData.AddPredefinedSemanticDomains(dataModel, projectData);
252+
await PreDefinedData.AddPredefinedCustomViews(dataModel, projectData);
253253
}
254254

255255
[GeneratedRegex("^[a-zA-Z0-9][a-zA-Z0-9-_]+$")]

backend/FwLite/LcmCrdt/CurrentProjectService.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,11 @@ async Task Execute()
111111
// Seed morph-types if missing (for existing projects created before morph-type support).
112112
// Must happen BEFORE FTS regeneration so headwords include morph-type tokens.
113113
// (querying Commits instead of MorphTypes, because the commit may not be projected yet)
114-
if (!await dbContext.Set<Commit>().AsNoTracking().AnyAsync(c => c.Id == PreDefinedData.MorphTypesSeedCommitId))
114+
var projectData = await dbContext.ProjectData.AsNoTracking().FirstAsync();
115+
if (!await dbContext.Set<Commit>().AsNoTracking().AnyAsync(c => c.Id == PreDefinedData.MorphTypesSeedCommitId(projectData.Id)))
115116
{
116117
var dataModel = services.GetRequiredService<DataModel>();
117-
var projectData = await dbContext.ProjectData.AsNoTracking().FirstAsync();
118-
await PreDefinedData.AddPredefinedMorphTypes(dataModel, projectData.ClientId);
118+
await PreDefinedData.AddPredefinedMorphTypes(dataModel, projectData);
119119
}
120120

121121
if (EntrySearchServiceFactory is not null)

backend/FwLite/LcmCrdt/LcmCrdt.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" />
2020
<PackageReference Include="Refit" />
2121
<PackageReference Include="Refit.HttpClientFactory" />
22+
<PackageReference Include="UUIDNext" />
2223
</ItemGroup>
2324
<ItemGroup Condition="$([MSBuild]::IsOsPlatform('linux'))">
2425
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" />

backend/FwLite/LcmCrdt/Objects/PreDefinedData.cs

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using LcmCrdt.Changes;
22
using SIL.Harmony;
3+
using UUIDNext;
34

45
namespace LcmCrdt.Objects;
56

@@ -15,20 +16,38 @@ public static class PreDefinedData
1516
public static readonly Guid AdjectivePartOfSpeechId = new("30d07580-5052-4d91-bc24-469b8b2d7df9");
1617
public static readonly Guid AdverbPartOfSpeechId = new("46e4fe08-ffa0-4c8b-bf98-2c56f38904d9");
1718

18-
internal static async Task AddPredefinedComplexFormTypes(DataModel dataModel, Guid clientId)
19+
// Seed commit-ids are derived per-project (UUIDv5 namespaced on projectId) so each project
20+
// owns its own row in LexBox's CrdtCommits table — a shared constant id would collide on the
21+
// primary key and the seed would get attributed to whichever project pushed first.
22+
public static Guid ComplexFormTypesSeedCommitId(Guid projectId) =>
23+
Uuid.NewNameBased(projectId, "complex-form-types-seed");
24+
25+
public static Guid SemanticDomainsSeedCommitId(Guid projectId) =>
26+
Uuid.NewNameBased(projectId, "semantic-domains-seed");
27+
28+
public static Guid PartsOfSpeechSeedCommitId(Guid projectId) =>
29+
Uuid.NewNameBased(projectId, "parts-of-speech-seed");
30+
31+
public static Guid CustomViewsSeedCommitId(Guid projectId) =>
32+
Uuid.NewNameBased(projectId, "custom-views-seed");
33+
34+
public static Guid MorphTypesSeedCommitId(Guid projectId) =>
35+
Uuid.NewNameBased(projectId, "morph-types-seed");
36+
37+
internal static async Task AddPredefinedComplexFormTypes(DataModel dataModel, ProjectData projectData)
1938
{
20-
await dataModel.AddChanges(clientId,
39+
await dataModel.AddChanges(projectData.ClientId,
2140
[
2241
new CreateComplexFormType(CompoundComplexFormTypeId, new MultiString() { { "en", "Compound" } } ),
2342
new CreateComplexFormType(UnspecifiedComplexFormTypeId, new MultiString() { { "en", "Unspecified" } })
2443
],
25-
new Guid("dc60d2a9-0cc2-48ed-803c-a238a14b6eae"));
44+
ComplexFormTypesSeedCommitId(projectData.Id));
2645
}
2746

28-
internal static async Task AddPredefinedSemanticDomains(DataModel dataModel, Guid clientId)
47+
internal static async Task AddPredefinedSemanticDomains(DataModel dataModel, ProjectData projectData)
2948
{
3049
//todo load from xml instead of hardcoding and use real IDs
31-
await dataModel.AddChanges(clientId,
50+
await dataModel.AddChanges(projectData.ClientId,
3251
[
3352
new CreateSemanticDomainChange(new Guid("63403699-07c1-43f3-a47c-069d6e4316e5"), new MultiString() { { "en", "Universe, Creation" } }, "1", true),
3453
new CreateSemanticDomainChange(new Guid("999581c4-1611-4acb-ae1b-5e6c1dfe6f0c"), new MultiString() { { "en", "Sky" } }, "1.1", true),
@@ -38,25 +57,25 @@ await dataModel.AddChanges(clientId,
3857
new CreateSemanticDomainChange(new Guid("46e4fe08-ffa0-4c8b-bf88-2c56f38904d5"), new MultiString() { { "en", "Head" } }, "2.1.1", false),
3958
new CreateSemanticDomainChange(new Guid("46e4fe08-ffa0-4c8b-bf88-2c56f38904d6"), new MultiString() { { "en", "Eye" } }, "2.1.1.1", false),
4059
],
41-
new Guid("023faebb-711b-4d2f-a14f-a15621fc66bc"));
60+
SemanticDomainsSeedCommitId(projectData.Id));
4261
}
4362

44-
public static async Task AddPredefinedPartsOfSpeech(DataModel dataModel, Guid clientId)
63+
public static async Task AddPredefinedPartsOfSpeech(DataModel dataModel, ProjectData projectData)
4564
{
4665
//todo load from xml instead of hardcoding
47-
await dataModel.AddChanges(clientId,
66+
await dataModel.AddChanges(projectData.ClientId,
4867
[
4968
new CreatePartOfSpeechChange(NounPartOfSpeechId, new MultiString() { { "en", "Noun" } }, true),
5069
new CreatePartOfSpeechChange(VerbPartOfSpeechId, new MultiString() { { "en", "Verb" } }, true),
5170
new CreatePartOfSpeechChange(AdjectivePartOfSpeechId, new MultiString() { { "en", "Adjective" } }, true),
5271
new CreatePartOfSpeechChange(AdverbPartOfSpeechId, new MultiString() { { "en", "Adverb" } }, true),
5372
],
54-
new Guid("023faebb-711b-4d2f-b34f-a15621fc66bb"));
73+
PartsOfSpeechSeedCommitId(projectData.Id));
5574
}
5675

57-
internal static async Task AddPredefinedCustomViews(DataModel dataModel, Guid clientId)
76+
internal static async Task AddPredefinedCustomViews(DataModel dataModel, ProjectData projectData)
5877
{
59-
await dataModel.AddChanges(clientId,
78+
await dataModel.AddChanges(projectData.ClientId,
6079
[
6180
new CreateCustomViewChange(
6281
new Guid("a1b2c3d4-e5f6-7890-abcd-ef1234567890"),
@@ -82,15 +101,13 @@ await dataModel.AddChanges(clientId,
82101
Analysis = [new ViewWritingSystem { WsId = "en" }]
83102
})
84103
],
85-
new Guid("b2c3d4e5-f6a7-8901-bcde-f12345678901"));
104+
CustomViewsSeedCommitId(projectData.Id));
86105
}
87106

88-
public static readonly Guid MorphTypesSeedCommitId = new("a7b2c3d4-e5f6-4a8b-9c0d-1e2f3a4b5c6d");
89-
90-
internal static async Task AddPredefinedMorphTypes(DataModel dataModel, Guid clientId)
107+
internal static async Task AddPredefinedMorphTypes(DataModel dataModel, ProjectData projectData)
91108
{
92-
await dataModel.AddChanges(clientId,
109+
await dataModel.AddChanges(projectData.ClientId,
93110
[.. CanonicalMorphTypes.All.Values.Select(mt => new CreateMorphTypeChange(mt))],
94-
MorphTypesSeedCommitId);
111+
MorphTypesSeedCommitId(projectData.Id));
95112
}
96113
}

0 commit comments

Comments
 (0)