Files
git.stella-ops.org/src/Policy/StellaOps.Policy.Engine/Subscriptions/SignalUpdateHandler.cs
2026-01-07 09:43:12 +02:00

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