using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Scripting; using ZB.MOM.WW.ScadaBridge.Commons.Messages.Scripting; namespace ZB.MOM.WW.ScadaBridge.InboundAPI; /// /// N1 (arch-review 06 round 2): the Inbound API consumer of the script-artifact /// invalidation contract (docs/plans/2026-07-08-script-artifact-invalidation-contract.md, /// handoff item (a)). Subscribes to on host /// start and, for every ApiMethod notification (e.g. a Transport bundle /// import overwriting method scripts, published post-commit), drops the compiled /// handler AND the known-bad record via /// , so the next request /// recompiles from the fresh row without waiting for the per-request /// revision-check self-heal. /// /// Fast-path only: the revision check in ExecuteAsync remains the /// correctness fallback — this bus is in-process/per-node and at-least-once /// (contract §4), so a missed or duplicate notification must always be harmless. /// The bus is optional (site roles and plain test hosts register none) — the /// subscriber then no-ops. SharedScript/Template notifications are ignored: this /// executor caches only ApiMethod handlers. /// /// public sealed class ScriptArtifactChangeSubscriber : IHostedService { private readonly InboundScriptExecutor _executor; private readonly ILogger _logger; private readonly IScriptArtifactChangeBus? _bus; private IDisposable? _subscription; /// Initializes the subscriber. /// The compiled-handler cache to invalidate. /// Logger instance. /// The change bus, or null when the host registers none (site roles, tests). public ScriptArtifactChangeSubscriber( InboundScriptExecutor executor, ILogger logger, IScriptArtifactChangeBus? bus = null) { _executor = executor; _logger = logger; _bus = bus; } /// public Task StartAsync(CancellationToken cancellationToken) { if (_bus is null) { _logger.LogDebug( "No IScriptArtifactChangeBus registered; inbound handler-cache freshness relies on the per-request revision check alone."); return Task.CompletedTask; } _subscription = _bus.Subscribe(OnChanged); return Task.CompletedTask; } /// public Task StopAsync(CancellationToken cancellationToken) { _subscription?.Dispose(); _subscription = null; return Task.CompletedTask; } private void OnChanged(ScriptArtifactsChanged notification) { if (!string.Equals( notification.ArtifactKind, ScriptArtifactKinds.ApiMethod, StringComparison.Ordinal)) { return; // only ApiMethod handlers are cached by this executor } foreach (var name in notification.Names) { _executor.InvalidateMethod(name); } _logger.LogInformation( "Invalidated {Count} inbound API method handler(s) after a {Source} script-artifact change.", notification.Names.Count, notification.Source); } }