217 lines
7.2 KiB
C#
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);
|
|
}
|