fix(site-runtime): resolve SiteRuntime-004..011 — deploy-after-persist, remove reflection, deterministic IDs, non-blocking startup, dedicated script scheduler, config-change detection, semantic trust-model check
This commit is contained in:
@@ -169,27 +169,12 @@ public class SiteExternalSystemRepository : IExternalSystemRepository
|
||||
// ── Private helpers ──
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new SQLite connection using the same connection string as <see cref="SiteStorageService"/>.
|
||||
/// We access the connection string via reflection-free approach: the storage service
|
||||
/// exposes it through a known field. Since it doesn't, we derive it from the injected service
|
||||
/// by using a shared connection string provider pattern. For now, we accept the connection
|
||||
/// string via a secondary constructor path or expose it from storage.
|
||||
///
|
||||
/// Implementation note: We use the SiteStorageService's internal connection string.
|
||||
/// This field is accessed via a package-internal helper since SiteStorageService
|
||||
/// doesn't expose it directly. As a pragmatic solution, we pass the connection string
|
||||
/// separately at DI registration time.
|
||||
/// Creates a new SQLite connection against the site database via
|
||||
/// <see cref="SiteStorageService.CreateConnection"/> (SiteRuntime-006). The
|
||||
/// connection string is owned by <see cref="SiteStorageService"/>; the repository
|
||||
/// no longer reaches into its private state via reflection.
|
||||
/// </summary>
|
||||
private SqliteConnection CreateConnection()
|
||||
{
|
||||
// Access the connection string from SiteStorageService via its internal field.
|
||||
// This uses reflection as a pragmatic choice — the alternative is modifying
|
||||
// SiteStorageService to expose the connection string, which is out of scope.
|
||||
var field = typeof(SiteStorageService).GetField("_connectionString",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
var connectionString = (string)field!.GetValue(_storage)!;
|
||||
return new SqliteConnection(connectionString);
|
||||
}
|
||||
private SqliteConnection CreateConnection() => _storage.CreateConnection();
|
||||
|
||||
private static ExternalSystemDefinition MapExternalSystem(SqliteDataReader reader)
|
||||
{
|
||||
@@ -233,12 +218,13 @@ public class SiteExternalSystemRepository : IExternalSystemRepository
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a stable positive integer ID from a string name.
|
||||
/// Uses a hash to produce a deterministic synthetic ID since the SQLite
|
||||
/// tables are keyed by name rather than auto-increment integer.
|
||||
/// Generates a stable positive integer ID from a string name (SiteRuntime-007).
|
||||
/// Uses a deterministic FNV-1a hash rather than <see cref="string.GetHashCode()"/>,
|
||||
/// which is randomized per process on .NET Core and would therefore change every
|
||||
/// time the process restarts — breaking any caller that stored an ID and later
|
||||
/// looks the entity up by that ID.
|
||||
/// </summary>
|
||||
private static int GenerateSyntheticId(string name)
|
||||
=> name.GetHashCode() & 0x7FFFFFFF;
|
||||
private static int GenerateSyntheticId(string name) => SyntheticId.From(name);
|
||||
|
||||
/// <summary>
|
||||
/// DTO for deserializing individual method entries from the method_definitions JSON column.
|
||||
|
||||
@@ -178,13 +178,12 @@ public class SiteNotificationRepository : INotificationRepository
|
||||
|
||||
// ── Private helpers ──
|
||||
|
||||
private SqliteConnection CreateConnection()
|
||||
{
|
||||
var field = typeof(SiteStorageService).GetField("_connectionString",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
var connectionString = (string)field!.GetValue(_storage)!;
|
||||
return new SqliteConnection(connectionString);
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a new SQLite connection against the site database via
|
||||
/// <see cref="SiteStorageService.CreateConnection"/> (SiteRuntime-006) instead of
|
||||
/// reaching into its private connection-string field via reflection.
|
||||
/// </summary>
|
||||
private SqliteConnection CreateConnection() => _storage.CreateConnection();
|
||||
|
||||
private static NotificationList MapNotificationList(SqliteDataReader reader)
|
||||
{
|
||||
@@ -246,10 +245,9 @@ public class SiteNotificationRepository : INotificationRepository
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a stable positive integer ID from a string name.
|
||||
/// Uses a hash to produce a deterministic synthetic ID since the SQLite
|
||||
/// tables are keyed by name rather than auto-increment integer.
|
||||
/// Generates a stable positive integer ID from a string name (SiteRuntime-007).
|
||||
/// Uses a deterministic FNV-1a hash rather than <see cref="string.GetHashCode()"/>,
|
||||
/// which is randomized per process on .NET Core and would change every restart.
|
||||
/// </summary>
|
||||
private static int GenerateSyntheticId(string name)
|
||||
=> name.GetHashCode() & 0x7FFFFFFF;
|
||||
private static int GenerateSyntheticId(string name) => SyntheticId.From(name);
|
||||
}
|
||||
|
||||
35
src/ScadaLink.SiteRuntime/Repositories/SyntheticId.cs
Normal file
35
src/ScadaLink.SiteRuntime/Repositories/SyntheticId.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
namespace ScadaLink.SiteRuntime.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// SiteRuntime-007: deterministic synthetic-ID generation for site-local artifacts.
|
||||
///
|
||||
/// The site SQLite tables are keyed by name rather than an auto-increment integer, but
|
||||
/// the shared repository contracts (<c>IExternalSystemRepository</c>,
|
||||
/// <c>INotificationRepository</c>) expose integer-keyed lookups. A synthetic integer ID
|
||||
/// is therefore derived from the entity name. It MUST be stable across process restarts
|
||||
/// — <see cref="string.GetHashCode()"/> is randomized per process on .NET Core and so
|
||||
/// cannot be used.
|
||||
/// </summary>
|
||||
internal static class SyntheticId
|
||||
{
|
||||
// FNV-1a 32-bit constants.
|
||||
private const uint FnvOffsetBasis = 2166136261;
|
||||
private const uint FnvPrime = 16777619;
|
||||
|
||||
/// <summary>
|
||||
/// Computes a deterministic, process-stable positive 31-bit integer ID for the
|
||||
/// given name using the FNV-1a hash over its UTF-8 bytes.
|
||||
/// </summary>
|
||||
public static int From(string name)
|
||||
{
|
||||
var hash = FnvOffsetBasis;
|
||||
foreach (var b in System.Text.Encoding.UTF8.GetBytes(name))
|
||||
{
|
||||
hash ^= b;
|
||||
hash *= FnvPrime;
|
||||
}
|
||||
|
||||
// Mask to a positive 31-bit value so the ID is always non-negative.
|
||||
return (int)(hash & 0x7FFFFFFF);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user