114 lines
3.3 KiB
C#
114 lines
3.3 KiB
C#
using Microsoft.Extensions.Logging;
|
|
using StellaOps.Policy.Determinization.Models;
|
|
using StellaOps.Policy.Engine.Gates;
|
|
|
|
namespace StellaOps.Policy.Engine.Subscriptions;
|
|
|
|
/// <summary>
|
|
/// Implementation of signal update handling.
|
|
/// </summary>
|
|
public sealed class SignalUpdateHandler : ISignalUpdateSubscription
|
|
{
|
|
private readonly IObservationRepository _observations;
|
|
private readonly IDeterminizationGate _gate;
|
|
private readonly IEventPublisher _eventPublisher;
|
|
private readonly ILogger<SignalUpdateHandler> _logger;
|
|
|
|
public SignalUpdateHandler(
|
|
IObservationRepository observations,
|
|
IDeterminizationGate gate,
|
|
IEventPublisher eventPublisher,
|
|
ILogger<SignalUpdateHandler> 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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Repository for CVE observations.
|
|
/// </summary>
|
|
public interface IObservationRepository
|
|
{
|
|
/// <summary>
|
|
/// Find observations by CVE ID and component PURL.
|
|
/// </summary>
|
|
Task<IReadOnlyList<CveObservation>> FindByCveAndPurlAsync(
|
|
string cveId,
|
|
string purl,
|
|
CancellationToken ct = default);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event publisher abstraction.
|
|
/// </summary>
|
|
public interface IEventPublisher
|
|
{
|
|
/// <summary>
|
|
/// Publish an event.
|
|
/// </summary>
|
|
Task PublishAsync<TEvent>(TEvent evt, CancellationToken ct = default)
|
|
where TEvent : class;
|
|
}
|
|
|
|
/// <summary>
|
|
/// CVE observation model.
|
|
/// </summary>
|
|
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; }
|
|
}
|