Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
74 lines
2.5 KiB
C#
74 lines
2.5 KiB
C#
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
|
|
namespace StellaOps.Policy.Engine.AdvisoryAI;
|
|
|
|
/// <summary>
|
|
/// In-memory store for Advisory AI knobs (POLICY-ENGINE-31-001).
|
|
/// </summary>
|
|
internal sealed class AdvisoryAiKnobsService
|
|
{
|
|
private readonly TimeProvider _timeProvider;
|
|
private readonly object _lock = new();
|
|
private AdvisoryAiKnobsProfile _current;
|
|
|
|
public AdvisoryAiKnobsService(TimeProvider timeProvider)
|
|
{
|
|
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
|
|
_current = BuildProfile(DefaultKnobs());
|
|
}
|
|
|
|
public AdvisoryAiKnobsProfile Get() => _current;
|
|
|
|
public AdvisoryAiKnobsProfile Set(IReadOnlyList<AdvisoryAiKnob> knobs)
|
|
{
|
|
var normalized = Normalize(knobs);
|
|
var profile = BuildProfile(normalized);
|
|
lock (_lock)
|
|
{
|
|
_current = profile;
|
|
}
|
|
|
|
return profile;
|
|
}
|
|
|
|
private AdvisoryAiKnobsProfile BuildProfile(IReadOnlyList<AdvisoryAiKnob> knobs)
|
|
{
|
|
var json = JsonSerializer.Serialize(knobs, new JsonSerializerOptions
|
|
{
|
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
|
WriteIndented = false
|
|
});
|
|
|
|
var hash = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(json)));
|
|
return new AdvisoryAiKnobsProfile(knobs, hash);
|
|
}
|
|
|
|
private IReadOnlyList<AdvisoryAiKnob> Normalize(IReadOnlyList<AdvisoryAiKnob> knobs)
|
|
{
|
|
var normalized = knobs
|
|
.Where(k => !string.IsNullOrWhiteSpace(k.Name))
|
|
.Select(k => new AdvisoryAiKnob(
|
|
Name: k.Name.Trim().ToLowerInvariant(),
|
|
DefaultValue: k.DefaultValue,
|
|
Min: k.Min,
|
|
Max: k.Max,
|
|
Step: k.Step <= 0 ? 0.001m : k.Step,
|
|
Description: string.IsNullOrWhiteSpace(k.Description) ? string.Empty : k.Description.Trim()))
|
|
.OrderBy(k => k.Name, StringComparer.Ordinal)
|
|
.ToList();
|
|
|
|
return normalized;
|
|
}
|
|
|
|
private static IReadOnlyList<AdvisoryAiKnob> DefaultKnobs() =>
|
|
new[]
|
|
{
|
|
new AdvisoryAiKnob("ai_signal_weight", 1.0m, 0m, 2m, 0.01m, "Weight applied to AI signals"),
|
|
new AdvisoryAiKnob("reachability_boost", 0.2m, 0m, 1m, 0.01m, "Boost when asset is reachable"),
|
|
new AdvisoryAiKnob("time_decay_half_life_days", 30m, 1m, 365m, 1m, "Half-life for decay"),
|
|
new AdvisoryAiKnob("evidence_freshness_threshold_hours", 72m, 1m, 720m, 1m, "Max evidence age")
|
|
};
|
|
}
|