using Microsoft.Extensions.Logging; using StellaOps.Policy.Determinization.Models; using StellaOps.Policy.Engine.Gates; namespace StellaOps.Policy.Engine.Subscriptions; /// /// Implementation of signal update handling. /// public sealed class SignalUpdateHandler : ISignalUpdateSubscription { private readonly IObservationRepository _observations; private readonly IDeterminizationGate _gate; private readonly IEventPublisher _eventPublisher; private readonly ILogger _logger; public SignalUpdateHandler( IObservationRepository observations, IDeterminizationGate gate, IEventPublisher eventPublisher, ILogger logger) { _observations = observations; _gate = gate; _eventPublisher = eventPublisher; _logger = logger; } public async Task HandleAsync(SignalUpdatedEvent evt, CancellationToken ct = default) { _logger.LogInformation( "Processing signal update: {EventType} for CVE {CveId} on {Purl}", evt.EventType, evt.CveId, evt.Purl); // Find observations affected by this signal var affected = await _observations.FindByCveAndPurlAsync(evt.CveId, evt.Purl, ct); foreach (var obs in affected) { try { await ReEvaluateObservationAsync(obs, evt, ct); } catch (Exception ex) { _logger.LogError(ex, "Failed to re-evaluate observation {ObservationId} after signal update", obs.Id); } } } private async Task ReEvaluateObservationAsync( CveObservation obs, SignalUpdatedEvent trigger, CancellationToken ct) { // This is a placeholder for re-evaluation logic // In a full implementation, this would: // 1. Build PolicyGateContext from observation // 2. Call gate.EvaluateDeterminizationAsync() // 3. Compare new verdict with old verdict // 4. Publish ObservationStateChangedEvent if state changed // 5. Update observation in repository _logger.LogDebug( "Re-evaluating observation {ObservationId} after {EventType}", obs.Id, trigger.EventType); await Task.CompletedTask; } } /// /// Repository for CVE observations. /// public interface IObservationRepository { /// /// Find observations by CVE ID and component PURL. /// Task> FindByCveAndPurlAsync( string cveId, string purl, CancellationToken ct = default); } /// /// Event publisher abstraction. /// public interface IEventPublisher { /// /// Publish an event. /// Task PublishAsync(TEvent evt, CancellationToken ct = default) where TEvent : class; } /// /// CVE observation model. /// public sealed record CveObservation { public required Guid Id { get; init; } public required string CveId { get; init; } public required string SubjectPurl { get; init; } public required ObservationState State { get; init; } public required DateTimeOffset ObservedAt { get; init; } }