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:
Joseph Doherty
2026-05-16 21:44:10 -04:00
parent 24a4a2d165
commit a88bec9376
17 changed files with 1112 additions and 150 deletions

View File

@@ -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.