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; }
}