Files
git.stella-ops.org/src/Policy/StellaOps.Policy.Engine/AdvisoryAI/AdvisoryAiKnobsService.cs
StellaOps Bot 150b3730ef
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
up
2025-11-24 07:52:25 +02:00

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