Files
git.stella-ops.org/src/Policy/StellaOps.PolicyDsl/SignalContext.cs
StellaOps Bot 8abbf9574d up
2025-11-27 21:10:06 +02:00

217 lines
7.2 KiB
C#

namespace StellaOps.PolicyDsl;
/// <summary>
/// Provides signal values for policy evaluation.
/// </summary>
public sealed class SignalContext
{
private readonly Dictionary<string, object?> _signals;
/// <summary>
/// Creates an empty signal context.
/// </summary>
public SignalContext()
{
_signals = new Dictionary<string, object?>(StringComparer.Ordinal);
}
/// <summary>
/// Creates a signal context with initial values.
/// </summary>
/// <param name="signals">Initial signal values.</param>
public SignalContext(IDictionary<string, object?> signals)
{
_signals = new Dictionary<string, object?>(signals, StringComparer.Ordinal);
}
/// <summary>
/// Gets whether a signal exists.
/// </summary>
/// <param name="name">The signal name.</param>
/// <returns>True if the signal exists.</returns>
public bool HasSignal(string name) => _signals.ContainsKey(name);
/// <summary>
/// Gets a signal value.
/// </summary>
/// <param name="name">The signal name.</param>
/// <returns>The signal value, or null if not found.</returns>
public object? GetSignal(string name) => _signals.TryGetValue(name, out var value) ? value : null;
/// <summary>
/// Gets a signal value as a specific type.
/// </summary>
/// <typeparam name="T">The expected type.</typeparam>
/// <param name="name">The signal name.</param>
/// <returns>The signal value, or default if not found or wrong type.</returns>
public T? GetSignal<T>(string name) => _signals.TryGetValue(name, out var value) && value is T t ? t : default;
/// <summary>
/// Sets a signal value.
/// </summary>
/// <param name="name">The signal name.</param>
/// <param name="value">The signal value.</param>
/// <returns>This context for chaining.</returns>
public SignalContext SetSignal(string name, object? value)
{
_signals[name] = value;
return this;
}
/// <summary>
/// Removes a signal.
/// </summary>
/// <param name="name">The signal name.</param>
/// <returns>This context for chaining.</returns>
public SignalContext RemoveSignal(string name)
{
_signals.Remove(name);
return this;
}
/// <summary>
/// Gets all signal names.
/// </summary>
public IEnumerable<string> SignalNames => _signals.Keys;
/// <summary>
/// Gets all signals as a read-only dictionary.
/// </summary>
public IReadOnlyDictionary<string, object?> Signals => _signals;
/// <summary>
/// Creates a copy of this context.
/// </summary>
/// <returns>A new context with the same signals.</returns>
public SignalContext Clone() => new(_signals);
/// <summary>
/// Creates a signal context builder for fluent construction.
/// </summary>
/// <returns>A new builder.</returns>
public static SignalContextBuilder Builder() => new();
}
/// <summary>
/// Builder for creating signal contexts with fluent API.
/// </summary>
public sealed class SignalContextBuilder
{
private readonly Dictionary<string, object?> _signals = new(StringComparer.Ordinal);
/// <summary>
/// Adds a signal to the context.
/// </summary>
/// <param name="name">The signal name.</param>
/// <param name="value">The signal value.</param>
/// <returns>This builder for chaining.</returns>
public SignalContextBuilder WithSignal(string name, object? value)
{
_signals[name] = value;
return this;
}
/// <summary>
/// Adds a boolean signal to the context.
/// </summary>
/// <param name="name">The signal name.</param>
/// <param name="value">The boolean value.</param>
/// <returns>This builder for chaining.</returns>
public SignalContextBuilder WithFlag(string name, bool value = true)
{
_signals[name] = value;
return this;
}
/// <summary>
/// Adds a numeric signal to the context.
/// </summary>
/// <param name="name">The signal name.</param>
/// <param name="value">The numeric value.</param>
/// <returns>This builder for chaining.</returns>
public SignalContextBuilder WithNumber(string name, decimal value)
{
_signals[name] = value;
return this;
}
/// <summary>
/// Adds a string signal to the context.
/// </summary>
/// <param name="name">The signal name.</param>
/// <param name="value">The string value.</param>
/// <returns>This builder for chaining.</returns>
public SignalContextBuilder WithString(string name, string value)
{
_signals[name] = value;
return this;
}
/// <summary>
/// Adds a nested object signal to the context.
/// </summary>
/// <param name="name">The signal name.</param>
/// <param name="properties">The nested properties.</param>
/// <returns>This builder for chaining.</returns>
public SignalContextBuilder WithObject(string name, IDictionary<string, object?> properties)
{
_signals[name] = new Dictionary<string, object?>(properties, StringComparer.Ordinal);
return this;
}
/// <summary>
/// Adds common finding signals.
/// </summary>
/// <param name="severity">The finding severity (e.g., "critical", "high", "medium", "low").</param>
/// <param name="confidence">The confidence score (0.0 to 1.0).</param>
/// <param name="cveId">Optional CVE identifier.</param>
/// <returns>This builder for chaining.</returns>
public SignalContextBuilder WithFinding(string severity, decimal confidence, string? cveId = null)
{
_signals["finding"] = new Dictionary<string, object?>(StringComparer.Ordinal)
{
["severity"] = severity,
["confidence"] = confidence,
["cve_id"] = cveId,
};
return this;
}
/// <summary>
/// Adds common reachability signals.
/// </summary>
/// <param name="state">The reachability state (e.g., "reachable", "unreachable", "unknown").</param>
/// <param name="confidence">The confidence score (0.0 to 1.0).</param>
/// <param name="hasRuntimeEvidence">Whether there is runtime evidence.</param>
/// <returns>This builder for chaining.</returns>
public SignalContextBuilder WithReachability(string state, decimal confidence, bool hasRuntimeEvidence = false)
{
_signals["reachability"] = new Dictionary<string, object?>(StringComparer.Ordinal)
{
["state"] = state,
["confidence"] = confidence,
["has_runtime_evidence"] = hasRuntimeEvidence,
};
return this;
}
/// <summary>
/// Adds common trust score signals.
/// </summary>
/// <param name="score">The trust score (0.0 to 1.0).</param>
/// <param name="verified">Whether the source is verified.</param>
/// <returns>This builder for chaining.</returns>
public SignalContextBuilder WithTrustScore(decimal score, bool verified = false)
{
_signals["trust_score"] = score;
_signals["trust_verified"] = verified;
return this;
}
/// <summary>
/// Builds the signal context.
/// </summary>
/// <returns>A new signal context with the configured signals.</returns>
public SignalContext Build() => new(_signals);
}