partly or unimplemented features - now implemented
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace StellaOps.AdvisoryAI.Explanation;
|
||||
|
||||
/// <summary>
|
||||
/// Runtime signal emitted by Zastava or compatible observers.
|
||||
/// </summary>
|
||||
public sealed record CompanionRuntimeSignal
|
||||
{
|
||||
public required string Source { get; init; }
|
||||
public required string Signal { get; init; }
|
||||
public required string Value { get; init; }
|
||||
public string? Path { get; init; }
|
||||
public double Confidence { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request for Codex/Zastava companion explanation composition.
|
||||
/// </summary>
|
||||
public sealed record CodexCompanionRequest
|
||||
{
|
||||
public required ExplanationRequest ExplanationRequest { get; init; }
|
||||
public IReadOnlyList<CompanionRuntimeSignal> RuntimeSignals { get; init; } = Array.Empty<CompanionRuntimeSignal>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response containing base explanation plus deterministic runtime highlights.
|
||||
/// </summary>
|
||||
public sealed record CodexCompanionResponse
|
||||
{
|
||||
public required string CompanionId { get; init; }
|
||||
public required string CompanionHash { get; init; }
|
||||
public required ExplanationResult Explanation { get; init; }
|
||||
public required ExplanationSummary CompanionSummary { get; init; }
|
||||
public required IReadOnlyList<CompanionRuntimeSignal> RuntimeHighlights { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Service that combines explanation output with Zastava runtime signals.
|
||||
/// </summary>
|
||||
public interface ICodexCompanionService
|
||||
{
|
||||
Task<CodexCompanionResponse> GenerateAsync(
|
||||
CodexCompanionRequest request,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deterministic implementation of the Codex/Zastava companion.
|
||||
/// </summary>
|
||||
public sealed class CodexZastavaCompanionService : ICodexCompanionService
|
||||
{
|
||||
private readonly IExplanationGenerator _explanationGenerator;
|
||||
|
||||
public CodexZastavaCompanionService(IExplanationGenerator explanationGenerator)
|
||||
{
|
||||
_explanationGenerator = explanationGenerator ?? throw new ArgumentNullException(nameof(explanationGenerator));
|
||||
}
|
||||
|
||||
public async Task<CodexCompanionResponse> GenerateAsync(
|
||||
CodexCompanionRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
ArgumentNullException.ThrowIfNull(request.ExplanationRequest);
|
||||
|
||||
var explanation = await _explanationGenerator
|
||||
.GenerateAsync(request.ExplanationRequest, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var highlights = NormalizeSignals(request.RuntimeSignals)
|
||||
.Take(5)
|
||||
.ToArray();
|
||||
|
||||
var companionSummary = BuildCompanionSummary(explanation.Summary, highlights);
|
||||
var companionHash = ComputeCompanionHash(explanation.OutputHash, highlights);
|
||||
|
||||
return new CodexCompanionResponse
|
||||
{
|
||||
CompanionId = $"companion:{companionHash}",
|
||||
CompanionHash = companionHash,
|
||||
Explanation = explanation,
|
||||
CompanionSummary = companionSummary,
|
||||
RuntimeHighlights = highlights,
|
||||
};
|
||||
}
|
||||
|
||||
private static IReadOnlyList<CompanionRuntimeSignal> NormalizeSignals(
|
||||
IReadOnlyList<CompanionRuntimeSignal> signals)
|
||||
{
|
||||
if (signals.Count == 0)
|
||||
{
|
||||
return Array.Empty<CompanionRuntimeSignal>();
|
||||
}
|
||||
|
||||
var deduplicated = new Dictionary<string, CompanionRuntimeSignal>(StringComparer.Ordinal);
|
||||
foreach (var signal in signals)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(signal.Source) ||
|
||||
string.IsNullOrWhiteSpace(signal.Signal) ||
|
||||
string.IsNullOrWhiteSpace(signal.Value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var normalized = new CompanionRuntimeSignal
|
||||
{
|
||||
Source = signal.Source.Trim(),
|
||||
Signal = signal.Signal.Trim(),
|
||||
Value = signal.Value.Trim(),
|
||||
Path = string.IsNullOrWhiteSpace(signal.Path) ? null : signal.Path.Trim(),
|
||||
Confidence = Math.Clamp(signal.Confidence, 0, 1),
|
||||
};
|
||||
|
||||
var key = string.Join("|", normalized.Source, normalized.Signal, normalized.Value, normalized.Path ?? string.Empty);
|
||||
if (deduplicated.TryGetValue(key, out var existing))
|
||||
{
|
||||
deduplicated[key] = normalized.Confidence >= existing.Confidence
|
||||
? normalized
|
||||
: existing;
|
||||
}
|
||||
else
|
||||
{
|
||||
deduplicated[key] = normalized;
|
||||
}
|
||||
}
|
||||
|
||||
return deduplicated.Values
|
||||
.OrderByDescending(static value => value.Confidence)
|
||||
.ThenBy(static value => value.Source, StringComparer.Ordinal)
|
||||
.ThenBy(static value => value.Signal, StringComparer.Ordinal)
|
||||
.ThenBy(static value => value.Value, StringComparer.Ordinal)
|
||||
.ThenBy(static value => value.Path, StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private static ExplanationSummary BuildCompanionSummary(
|
||||
ExplanationSummary baseSummary,
|
||||
IReadOnlyList<CompanionRuntimeSignal> highlights)
|
||||
{
|
||||
var line2 = highlights.Count == 0
|
||||
? "No Zastava runtime signals were provided; verdict is based on static evidence."
|
||||
: $"Runtime signal {highlights[0].Source}/{highlights[0].Signal} indicates '{highlights[0].Value}'.";
|
||||
|
||||
return new ExplanationSummary
|
||||
{
|
||||
Line1 = $"Companion: {baseSummary.Line1}",
|
||||
Line2 = line2,
|
||||
Line3 = baseSummary.Line3,
|
||||
};
|
||||
}
|
||||
|
||||
private static string ComputeCompanionHash(
|
||||
string explanationOutputHash,
|
||||
IReadOnlyList<CompanionRuntimeSignal> highlights)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append(explanationOutputHash).Append('\n');
|
||||
|
||||
foreach (var highlight in highlights)
|
||||
{
|
||||
builder.Append(highlight.Source).Append('|')
|
||||
.Append(highlight.Signal).Append('|')
|
||||
.Append(highlight.Value).Append('|')
|
||||
.Append(highlight.Path ?? string.Empty).Append('|')
|
||||
.Append(highlight.Confidence.ToString("F4", System.Globalization.CultureInfo.InvariantCulture))
|
||||
.Append('\n');
|
||||
}
|
||||
|
||||
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(builder.ToString()));
|
||||
return Convert.ToHexString(hash).ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
@@ -11,3 +11,6 @@ Source of truth: `docs/implplan/SPRINT_20260113_005_ADVISORYAI_controlled_conver
|
||||
| AIAI-CHAT-AUDIT-0001 | DONE | Persist chat audit tables and logger. |
|
||||
| AUDIT-TESTGAP-ADVISORYAI-0001 | DONE | Added worker and unified plugin adapter tests. |
|
||||
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
||||
|
||||
| SPRINT_20260208_003-CORE | DONE | Codex/Zastava companion core service for deterministic runtime-aware explanation composition. |
|
||||
|
||||
|
||||
Reference in New Issue
Block a user