feat: wire SQLite replication between site nodes and fix ConfigurationDatabase tests

Add SiteReplicationActor (runs on every site node) to replicate deployed
configs and store-and-forward buffer operations to the standby peer via
cluster member discovery and fire-and-forget Tell. Wire ReplicationService
handler and pass replication actor to DeploymentManagerActor singleton.

Fix 5 pre-existing ConfigurationDatabase test failures: RowVersion NOT NULL
on SQLite, stale migration name assertion, and seed data count mismatch.
This commit is contained in:
Joseph Doherty
2026-03-18 08:28:02 -04:00
parent f063fb1ca3
commit eb8ead58d2
23 changed files with 707 additions and 33 deletions

View File

@@ -90,6 +90,8 @@ public class ManagementActor : ReceiveActor
or ValidateTemplateCommand
or CreateExternalSystemCommand or UpdateExternalSystemCommand
or DeleteExternalSystemCommand
or CreateExternalSystemMethodCommand or UpdateExternalSystemMethodCommand
or DeleteExternalSystemMethodCommand
or CreateNotificationListCommand or UpdateNotificationListCommand
or DeleteNotificationListCommand
or UpdateSmtpConfigCommand
@@ -178,6 +180,11 @@ public class ManagementActor : ReceiveActor
CreateExternalSystemCommand cmd => await HandleCreateExternalSystem(sp, cmd),
UpdateExternalSystemCommand cmd => await HandleUpdateExternalSystem(sp, cmd),
DeleteExternalSystemCommand cmd => await HandleDeleteExternalSystem(sp, cmd),
ListExternalSystemMethodsCommand cmd => await HandleListExternalSystemMethods(sp, cmd),
GetExternalSystemMethodCommand cmd => await HandleGetExternalSystemMethod(sp, cmd),
CreateExternalSystemMethodCommand cmd => await HandleCreateExternalSystemMethod(sp, cmd),
UpdateExternalSystemMethodCommand cmd => await HandleUpdateExternalSystemMethod(sp, cmd),
DeleteExternalSystemMethodCommand cmd => await HandleDeleteExternalSystemMethod(sp, cmd),
// Notification Lists
ListNotificationListsCommand => await HandleListNotificationLists(sp),
@@ -568,6 +575,55 @@ public class ManagementActor : ReceiveActor
return true;
}
private static async Task<object?> HandleListExternalSystemMethods(IServiceProvider sp, ListExternalSystemMethodsCommand cmd)
{
var repo = sp.GetRequiredService<IExternalSystemRepository>();
return await repo.GetMethodsByExternalSystemIdAsync(cmd.ExternalSystemId);
}
private static async Task<object?> HandleGetExternalSystemMethod(IServiceProvider sp, GetExternalSystemMethodCommand cmd)
{
var repo = sp.GetRequiredService<IExternalSystemRepository>();
return await repo.GetExternalSystemMethodByIdAsync(cmd.MethodId);
}
private static async Task<object?> HandleCreateExternalSystemMethod(IServiceProvider sp, CreateExternalSystemMethodCommand cmd)
{
var repo = sp.GetRequiredService<IExternalSystemRepository>();
var method = new ExternalSystemMethod(cmd.Name, cmd.HttpMethod, cmd.Path)
{
ExternalSystemDefinitionId = cmd.ExternalSystemId,
ParameterDefinitions = cmd.ParameterDefinitions,
ReturnDefinition = cmd.ReturnDefinition
};
await repo.AddExternalSystemMethodAsync(method);
await repo.SaveChangesAsync();
return method;
}
private static async Task<object?> HandleUpdateExternalSystemMethod(IServiceProvider sp, UpdateExternalSystemMethodCommand cmd)
{
var repo = sp.GetRequiredService<IExternalSystemRepository>();
var method = await repo.GetExternalSystemMethodByIdAsync(cmd.MethodId)
?? throw new InvalidOperationException($"ExternalSystemMethod with ID {cmd.MethodId} not found.");
if (cmd.Name != null) method.Name = cmd.Name;
if (cmd.HttpMethod != null) method.HttpMethod = cmd.HttpMethod;
if (cmd.Path != null) method.Path = cmd.Path;
if (cmd.ParameterDefinitions != null) method.ParameterDefinitions = cmd.ParameterDefinitions;
if (cmd.ReturnDefinition != null) method.ReturnDefinition = cmd.ReturnDefinition;
await repo.UpdateExternalSystemMethodAsync(method);
await repo.SaveChangesAsync();
return method;
}
private static async Task<object?> HandleDeleteExternalSystemMethod(IServiceProvider sp, DeleteExternalSystemMethodCommand cmd)
{
var repo = sp.GetRequiredService<IExternalSystemRepository>();
await repo.DeleteExternalSystemMethodAsync(cmd.MethodId);
await repo.SaveChangesAsync();
return true;
}
// ========================================================================
// Notification handlers
// ========================================================================
@@ -850,7 +906,9 @@ public class ManagementActor : ReceiveActor
{
TriggerType = cmd.TriggerType,
TriggerConfiguration = cmd.TriggerConfiguration,
IsLocked = cmd.IsLocked
IsLocked = cmd.IsLocked,
ParameterDefinitions = cmd.ParameterDefinitions,
ReturnDefinition = cmd.ReturnDefinition
};
var result = await svc.AddScriptAsync(cmd.TemplateId, script, user);
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);
@@ -863,7 +921,9 @@ public class ManagementActor : ReceiveActor
{
TriggerType = cmd.TriggerType,
TriggerConfiguration = cmd.TriggerConfiguration,
IsLocked = cmd.IsLocked
IsLocked = cmd.IsLocked,
ParameterDefinitions = cmd.ParameterDefinitions,
ReturnDefinition = cmd.ReturnDefinition
};
var result = await svc.UpdateScriptAsync(cmd.ScriptId, script, user);
return result.IsSuccess ? result.Value : throw new InvalidOperationException(result.Error);