feat(esg): TimeoutSeconds travels the artifact pipeline (additive contract) into site SQLite so site-side calls honor it
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
This commit is contained in:
@@ -5,4 +5,5 @@ public record ExternalSystemArtifact(
|
|||||||
string EndpointUrl,
|
string EndpointUrl,
|
||||||
string AuthType,
|
string AuthType,
|
||||||
string? AuthConfiguration,
|
string? AuthConfiguration,
|
||||||
string? MethodDefinitionsJson);
|
string? MethodDefinitionsJson,
|
||||||
|
int TimeoutSeconds = 0);
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ public class ArtifactDeploymentService
|
|||||||
}))
|
}))
|
||||||
: null;
|
: null;
|
||||||
externalSystemArtifacts.Add(new ExternalSystemArtifact(
|
externalSystemArtifacts.Add(new ExternalSystemArtifact(
|
||||||
es.Name, es.EndpointUrl, es.AuthType, es.AuthConfiguration, methodsJson));
|
es.Name, es.EndpointUrl, es.AuthType, es.AuthConfiguration, methodsJson, es.TimeoutSeconds));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map database connections
|
// Map database connections
|
||||||
|
|||||||
@@ -1885,7 +1885,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
|||||||
foreach (var es in command.ExternalSystems)
|
foreach (var es in command.ExternalSystems)
|
||||||
{
|
{
|
||||||
await _storage.StoreExternalSystemAsync(es.Name, es.EndpointUrl,
|
await _storage.StoreExternalSystemAsync(es.Name, es.EndpointUrl,
|
||||||
es.AuthType, es.AuthConfiguration, es.MethodDefinitionsJson);
|
es.AuthType, es.AuthConfiguration, es.MethodDefinitionsJson, es.TimeoutSeconds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -349,7 +349,7 @@ public class SiteReplicationActor : ReceiveActor
|
|||||||
|
|
||||||
if (command.ExternalSystems != null)
|
if (command.ExternalSystems != null)
|
||||||
foreach (var es in command.ExternalSystems)
|
foreach (var es in command.ExternalSystems)
|
||||||
await _storage.StoreExternalSystemAsync(es.Name, es.EndpointUrl, es.AuthType, es.AuthConfiguration, es.MethodDefinitionsJson);
|
await _storage.StoreExternalSystemAsync(es.Name, es.EndpointUrl, es.AuthType, es.AuthConfiguration, es.MethodDefinitionsJson, es.TimeoutSeconds);
|
||||||
|
|
||||||
if (command.DatabaseConnections != null)
|
if (command.DatabaseConnections != null)
|
||||||
foreach (var db in command.DatabaseConnections)
|
foreach (var db in command.DatabaseConnections)
|
||||||
|
|||||||
@@ -174,6 +174,10 @@ public class SiteStorageService
|
|||||||
// Native-alarm display metadata (UA4) — restored on rehydration so a persisted condition
|
// Native-alarm display metadata (UA4) — restored on rehydration so a persisted condition
|
||||||
// renders fully (type/category/message/values) before the first source snapshot arrives.
|
// renders fully (type/category/message/values) before the first source snapshot arrives.
|
||||||
await TryAddColumnAsync(connection, "native_alarm_state", "metadata_json", "TEXT");
|
await TryAddColumnAsync(connection, "native_alarm_state", "metadata_json", "TEXT");
|
||||||
|
|
||||||
|
// Per-external-system call timeout (ExternalSystemGateway Timeout) — carried through the
|
||||||
|
// artifact pipeline so site-side calls honor it; 0 = use the site default.
|
||||||
|
await TryAddColumnAsync(connection, "external_systems", "timeout_seconds", "INTEGER NOT NULL DEFAULT 0");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task TryAddColumnAsync(SqliteConnection connection, string table, string column, string type)
|
private async Task TryAddColumnAsync(SqliteConnection connection, string table, string column, string type)
|
||||||
@@ -716,22 +720,25 @@ public class SiteStorageService
|
|||||||
/// <param name="authType">The authentication type (e.g., 'ApiKey', 'BasicAuth').</param>
|
/// <param name="authType">The authentication type (e.g., 'ApiKey', 'BasicAuth').</param>
|
||||||
/// <param name="authConfig">Authentication configuration JSON, if applicable.</param>
|
/// <param name="authConfig">Authentication configuration JSON, if applicable.</param>
|
||||||
/// <param name="methodDefs">JSON representation of available method definitions, if any.</param>
|
/// <param name="methodDefs">JSON representation of available method definitions, if any.</param>
|
||||||
|
/// <param name="timeoutSeconds">Per-system call timeout in seconds (0 = use the site default).</param>
|
||||||
/// <returns>A task that completes when the external system definition has been stored or updated.</returns>
|
/// <returns>A task that completes when the external system definition has been stored or updated.</returns>
|
||||||
public async Task StoreExternalSystemAsync(
|
public async Task StoreExternalSystemAsync(
|
||||||
string name, string endpointUrl, string authType, string? authConfig, string? methodDefs)
|
string name, string endpointUrl, string authType, string? authConfig, string? methodDefs,
|
||||||
|
int timeoutSeconds = 0)
|
||||||
{
|
{
|
||||||
await using var connection = new SqliteConnection(_connectionString);
|
await using var connection = new SqliteConnection(_connectionString);
|
||||||
await connection.OpenAsync();
|
await connection.OpenAsync();
|
||||||
|
|
||||||
await using var command = connection.CreateCommand();
|
await using var command = connection.CreateCommand();
|
||||||
command.CommandText = @"
|
command.CommandText = @"
|
||||||
INSERT INTO external_systems (name, endpoint_url, auth_type, auth_configuration, method_definitions, updated_at)
|
INSERT INTO external_systems (name, endpoint_url, auth_type, auth_configuration, method_definitions, timeout_seconds, updated_at)
|
||||||
VALUES (@name, @url, @authType, @authConfig, @methodDefs, @updatedAt)
|
VALUES (@name, @url, @authType, @authConfig, @methodDefs, @timeoutSeconds, @updatedAt)
|
||||||
ON CONFLICT(name) DO UPDATE SET
|
ON CONFLICT(name) DO UPDATE SET
|
||||||
endpoint_url = excluded.endpoint_url,
|
endpoint_url = excluded.endpoint_url,
|
||||||
auth_type = excluded.auth_type,
|
auth_type = excluded.auth_type,
|
||||||
auth_configuration = excluded.auth_configuration,
|
auth_configuration = excluded.auth_configuration,
|
||||||
method_definitions = excluded.method_definitions,
|
method_definitions = excluded.method_definitions,
|
||||||
|
timeout_seconds = excluded.timeout_seconds,
|
||||||
updated_at = excluded.updated_at";
|
updated_at = excluded.updated_at";
|
||||||
|
|
||||||
command.Parameters.AddWithValue("@name", name);
|
command.Parameters.AddWithValue("@name", name);
|
||||||
@@ -739,6 +746,7 @@ public class SiteStorageService
|
|||||||
command.Parameters.AddWithValue("@authType", authType);
|
command.Parameters.AddWithValue("@authType", authType);
|
||||||
command.Parameters.AddWithValue("@authConfig", (object?)authConfig ?? DBNull.Value);
|
command.Parameters.AddWithValue("@authConfig", (object?)authConfig ?? DBNull.Value);
|
||||||
command.Parameters.AddWithValue("@methodDefs", (object?)methodDefs ?? DBNull.Value);
|
command.Parameters.AddWithValue("@methodDefs", (object?)methodDefs ?? DBNull.Value);
|
||||||
|
command.Parameters.AddWithValue("@timeoutSeconds", timeoutSeconds);
|
||||||
command.Parameters.AddWithValue("@updatedAt", DateTimeOffset.UtcNow.ToString("O"));
|
command.Parameters.AddWithValue("@updatedAt", DateTimeOffset.UtcNow.ToString("O"));
|
||||||
|
|
||||||
await command.ExecuteNonQueryAsync();
|
await command.ExecuteNonQueryAsync();
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ public class SiteExternalSystemRepository : IExternalSystemRepository
|
|||||||
|
|
||||||
await using var command = connection.CreateCommand();
|
await using var command = connection.CreateCommand();
|
||||||
command.CommandText = @"
|
command.CommandText = @"
|
||||||
SELECT name, endpoint_url, auth_type, auth_configuration
|
SELECT name, endpoint_url, auth_type, auth_configuration, timeout_seconds
|
||||||
FROM external_systems";
|
FROM external_systems";
|
||||||
|
|
||||||
var results = new List<ExternalSystemDefinition>();
|
var results = new List<ExternalSystemDefinition>();
|
||||||
@@ -68,7 +68,7 @@ public class SiteExternalSystemRepository : IExternalSystemRepository
|
|||||||
|
|
||||||
await using var command = connection.CreateCommand();
|
await using var command = connection.CreateCommand();
|
||||||
command.CommandText = @"
|
command.CommandText = @"
|
||||||
SELECT name, endpoint_url, auth_type, auth_configuration
|
SELECT name, endpoint_url, auth_type, auth_configuration, timeout_seconds
|
||||||
FROM external_systems
|
FROM external_systems
|
||||||
WHERE name = @name";
|
WHERE name = @name";
|
||||||
command.Parameters.AddWithValue("@name", name);
|
command.Parameters.AddWithValue("@name", name);
|
||||||
@@ -263,7 +263,8 @@ public class SiteExternalSystemRepository : IExternalSystemRepository
|
|||||||
authType: reader.GetString(2))
|
authType: reader.GetString(2))
|
||||||
{
|
{
|
||||||
Id = GenerateSyntheticId(name),
|
Id = GenerateSyntheticId(name),
|
||||||
AuthConfiguration = reader.IsDBNull(3) ? null : reader.GetString(3)
|
AuthConfiguration = reader.IsDBNull(3) ? null : reader.GetString(3),
|
||||||
|
TimeoutSeconds = reader.IsDBNull(4) ? 0 : reader.GetInt32(4)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,26 @@ public class SiteRepositoryTests : IDisposable
|
|||||||
Assert.Equal("https://api.example.com", all[0].EndpointUrl);
|
Assert.Equal("https://api.example.com", all[0].EndpointUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ExternalSystemGateway (Timeout): the per-system <c>TimeoutSeconds</c> stored via
|
||||||
|
/// <see cref="SiteStorageService.StoreExternalSystemAsync"/> must round-trip through the
|
||||||
|
/// site SQLite store and back out via the repository, so site-side calls honor it.
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public async Task SiteStorage_ExternalSystem_TimeoutSeconds_RoundTrips()
|
||||||
|
{
|
||||||
|
var storage = NewStorage();
|
||||||
|
await storage.InitializeAsync();
|
||||||
|
await storage.StoreExternalSystemAsync(
|
||||||
|
"WeatherApi", "https://api.example.com", "ApiKey", "{\"key\":\"x\"}", null, 7);
|
||||||
|
|
||||||
|
var repo = new SiteExternalSystemRepository(storage);
|
||||||
|
var found = await repo.GetExternalSystemByNameAsync("WeatherApi");
|
||||||
|
|
||||||
|
Assert.NotNull(found);
|
||||||
|
Assert.Equal(7, found!.TimeoutSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SiteRuntime-007: the synthetic ID for an external system must be identical when
|
/// SiteRuntime-007: the synthetic ID for an external system must be identical when
|
||||||
/// the storage service and repository are re-created (simulating a process restart).
|
/// the storage service and repository are re-created (simulating a process restart).
|
||||||
|
|||||||
Reference in New Issue
Block a user