feat: complete gRPC streaming channel — site host, docker config, docs, integration tests

Switch site host to WebApplicationBuilder with Kestrel HTTP/2 gRPC server,
add GrpcPort/keepalive config, wire SiteStreamManager as ISiteStreamSubscriber,
expose gRPC ports in docker-compose, add site seed script, update all 10
requirement docs + CLAUDE.md + README.md for the new dual-transport architecture.
This commit is contained in:
Joseph Doherty
2026-03-21 12:38:33 -04:00
parent 3fe3c4161b
commit 416a03b782
34 changed files with 728 additions and 156 deletions
@@ -3,6 +3,7 @@ using Akka.Actor;
using Akka.Streams;
using Akka.Streams.Dsl;
using Microsoft.Extensions.Logging;
using ScadaLink.Communication.Grpc;
using ScadaLink.Commons.Messages.Streaming;
namespace ScadaLink.SiteRuntime.Streaming;
@@ -13,10 +14,12 @@ namespace ScadaLink.SiteRuntime.Streaming;
/// Subscribers get per-subscriber bounded buffers with drop-oldest overflow.
///
/// Filterable by instance name for debug view (WP-25).
/// Implements ISiteStreamSubscriber so the gRPC server can subscribe actors
/// to instance events without referencing SiteRuntime directly.
/// </summary>
public class SiteStreamManager
public class SiteStreamManager : ISiteStreamSubscriber
{
private readonly ActorSystem _system;
private ActorSystem? _system;
private readonly int _bufferSize;
private readonly ILogger<SiteStreamManager> _logger;
private readonly object _lock = new();
@@ -25,20 +28,21 @@ public class SiteStreamManager
private readonly Dictionary<string, SubscriptionInfo> _subscriptions = new();
public SiteStreamManager(
ActorSystem system,
SiteRuntimeOptions options,
ILogger<SiteStreamManager> logger)
{
_system = system;
_bufferSize = options.StreamBufferSize;
_logger = logger;
}
/// <summary>
/// Initializes the stream source. Must be called after ActorSystem is ready.
/// The ActorSystem is passed here rather than via the constructor so that
/// SiteStreamManager can be created by DI before the actor system exists.
/// </summary>
public void Initialize()
public void Initialize(ActorSystem system)
{
_system = system;
var materializer = _system.Materializer();
var source = Source.ActorRef<ISiteStreamEvent>(