sprints completion. new product advisories prepared

This commit is contained in:
master
2026-01-16 16:30:03 +02:00
parent a927d924e3
commit 4ca3ce8fb4
255 changed files with 42434 additions and 1020 deletions

View File

@@ -0,0 +1,237 @@
// -----------------------------------------------------------------------------
// AiCodeGuardEvidenceContext.cs
// Sprint: SPRINT_20260112_010_POLICY_ai_code_guard_policy
// Task: POLICY-AIGUARD-001 - AI Code Guard evidence context
// -----------------------------------------------------------------------------
using System.Collections.Immutable;
namespace StellaOps.Policy.AiCodeGuard;
/// <summary>
/// Context for AI Code Guard evidence evaluation.
/// Provides accessors for common policy signal patterns.
/// </summary>
public sealed class AiCodeGuardEvidenceContext
{
private readonly IAiCodeGuardEvidenceProvider _provider;
private readonly ImmutableList<AiCodeGuardFinding> _activeFindings;
/// <summary>
/// Creates a new AI Code Guard evidence context.
/// </summary>
/// <param name="provider">The evidence provider.</param>
public AiCodeGuardEvidenceContext(IAiCodeGuardEvidenceProvider provider)
{
_provider = provider ?? throw new ArgumentNullException(nameof(provider));
// Filter out suppressed findings
var suppressed = provider.Overrides
.Where(o => o.Action.Equals("suppress", StringComparison.OrdinalIgnoreCase) ||
o.Action.Equals("false-positive", StringComparison.OrdinalIgnoreCase))
.Select(o => o.FindingId)
.ToHashSet(StringComparer.Ordinal);
_activeFindings = provider.Findings
.Where(f => !suppressed.Contains(f.Id))
.ToImmutableList();
}
/// <summary>
/// Gets all findings (including suppressed).
/// </summary>
public ImmutableList<AiCodeGuardFinding> AllFindings => _provider.Findings;
/// <summary>
/// Gets active findings (excluding suppressed).
/// </summary>
public ImmutableList<AiCodeGuardFinding> ActiveFindings => _activeFindings;
/// <summary>
/// Gets all overrides.
/// </summary>
public ImmutableList<AiCodeGuardOverrideRecord> Overrides => _provider.Overrides;
/// <summary>
/// Gets whether there are any findings.
/// </summary>
public bool HasAnyFinding => _provider.Findings.Count > 0;
/// <summary>
/// Gets whether there are any active (non-suppressed) findings.
/// </summary>
public bool HasActiveFinding => _activeFindings.Count > 0;
/// <summary>
/// Gets the total finding count.
/// </summary>
public int TotalFindingCount => _provider.Findings.Count;
/// <summary>
/// Gets the active finding count.
/// </summary>
public int ActiveFindingCount => _activeFindings.Count;
/// <summary>
/// Gets the verdict status.
/// </summary>
public AiCodeGuardVerdictStatus VerdictStatus => _provider.VerdictStatus;
/// <summary>
/// Gets the AI-generated code percentage.
/// </summary>
public double? AiGeneratedPercentage => _provider.AiGeneratedPercentage;
/// <summary>
/// Gets the scanner info.
/// </summary>
public AiCodeGuardScannerInfo? ScannerInfo => _provider.ScannerInfo;
/// <summary>
/// Checks if there are active findings with the specified severity.
/// </summary>
public bool HasFindingWithSeverity(string severity)
{
return _activeFindings.Any(f =>
f.Severity.Equals(severity, StringComparison.OrdinalIgnoreCase));
}
/// <summary>
/// Gets the count of active findings with the specified severity.
/// </summary>
public int GetFindingCountBySeverity(string severity)
{
return _activeFindings.Count(f =>
f.Severity.Equals(severity, StringComparison.OrdinalIgnoreCase));
}
/// <summary>
/// Checks if there are active findings with the specified category.
/// </summary>
public bool HasFindingWithCategory(string category)
{
return _activeFindings.Any(f =>
f.Category.Equals(category, StringComparison.OrdinalIgnoreCase));
}
/// <summary>
/// Gets the count of active findings with the specified category.
/// </summary>
public int GetFindingCountByCategory(string category)
{
return _activeFindings.Count(f =>
f.Category.Equals(category, StringComparison.OrdinalIgnoreCase));
}
/// <summary>
/// Checks if there are active findings with confidence above threshold.
/// </summary>
public bool HasFindingWithConfidenceAbove(double threshold)
{
return _activeFindings.Any(f => f.Confidence >= threshold);
}
/// <summary>
/// Gets the count of active findings with confidence above threshold.
/// </summary>
public int GetFindingCountWithConfidenceAbove(double threshold)
{
return _activeFindings.Count(f => f.Confidence >= threshold);
}
/// <summary>
/// Gets the highest severity among active findings.
/// </summary>
public string? HighestSeverity
{
get
{
if (_activeFindings.Count == 0)
return null;
var severityOrder = new[] { "critical", "high", "medium", "low", "info" };
foreach (var severity in severityOrder)
{
if (HasFindingWithSeverity(severity))
return severity;
}
return _activeFindings[0].Severity;
}
}
/// <summary>
/// Gets the average confidence of active findings.
/// </summary>
public double? AverageConfidence
{
get
{
if (_activeFindings.Count == 0)
return null;
return _activeFindings.Average(f => f.Confidence);
}
}
/// <summary>
/// Gets the count of active overrides.
/// </summary>
public int ActiveOverrideCount
{
get
{
var now = DateTimeOffset.UtcNow;
return _provider.Overrides.Count(o =>
!o.ExpiresAt.HasValue || o.ExpiresAt.Value > now);
}
}
/// <summary>
/// Gets the count of expired overrides.
/// </summary>
public int ExpiredOverrideCount
{
get
{
var now = DateTimeOffset.UtcNow;
return _provider.Overrides.Count(o =>
o.ExpiresAt.HasValue && o.ExpiresAt.Value <= now);
}
}
/// <summary>
/// Checks if all findings in specified paths are suppressed.
/// </summary>
public bool AllFindingsInPathsSuppressed(IReadOnlyList<string> pathPatterns)
{
var matchingFindings = _provider.Findings
.Where(f => pathPatterns.Any(p => MatchesGlob(f.FilePath, p)));
return matchingFindings.All(f =>
_provider.Overrides.Any(o =>
o.FindingId == f.Id &&
(o.Action.Equals("suppress", StringComparison.OrdinalIgnoreCase) ||
o.Action.Equals("false-positive", StringComparison.OrdinalIgnoreCase))));
}
private static bool MatchesGlob(string path, string pattern)
{
// Simple glob matching for common patterns
if (pattern == "*" || pattern == "**")
return true;
if (pattern.StartsWith("**/", StringComparison.Ordinal))
{
var suffix = pattern[3..];
return path.EndsWith(suffix, StringComparison.OrdinalIgnoreCase) ||
path.Contains("/" + suffix, StringComparison.OrdinalIgnoreCase);
}
if (pattern.EndsWith("/**", StringComparison.Ordinal))
{
var prefix = pattern[..^3];
return path.StartsWith(prefix, StringComparison.OrdinalIgnoreCase);
}
return path.Equals(pattern, StringComparison.OrdinalIgnoreCase);
}
}

View File

@@ -0,0 +1,330 @@
// -----------------------------------------------------------------------------
// AiCodeGuardSignalBinder.cs
// Sprint: SPRINT_20260112_010_POLICY_ai_code_guard_policy
// Task: POLICY-AIGUARD-001/002 - AI Code Guard signal binding for policy evaluation
// -----------------------------------------------------------------------------
using System.Collections.Immutable;
using System.Globalization;
namespace StellaOps.Policy.AiCodeGuard;
/// <summary>
/// Binds AI Code Guard evidence to policy evaluation signals.
/// This class converts AI code guard findings, verdicts, and override metadata
/// into signals that can be evaluated by the PolicyDsl SignalContext.
///
/// <para>
/// Available signals after binding:
/// <list type="bullet">
/// <item><c>guard.has_finding</c> - true if any finding exists</item>
/// <item><c>guard.has_active_finding</c> - true if any active (non-suppressed) finding exists</item>
/// <item><c>guard.count</c> - total number of findings</item>
/// <item><c>guard.active_count</c> - number of active findings</item>
/// <item><c>guard.severity.critical</c> - true if any critical finding exists</item>
/// <item><c>guard.severity.high</c> - true if any high severity finding exists</item>
/// <item><c>guard.severity.medium</c> - true if any medium severity finding exists</item>
/// <item><c>guard.severity.low</c> - true if any low severity finding exists</item>
/// <item><c>guard.category.ai_generated</c> - true if any AI-generated finding exists</item>
/// <item><c>guard.category.insecure_pattern</c> - true if any insecure pattern finding exists</item>
/// <item><c>guard.category.hallucination</c> - true if any hallucination finding exists</item>
/// <item><c>guard.category.license_risk</c> - true if any license risk finding exists</item>
/// <item><c>guard.verdict</c> - the verdict status (pass, pass_with_warnings, fail, error)</item>
/// <item><c>guard.ai_percentage</c> - estimated AI-generated code percentage</item>
/// <item><c>guard.override.count</c> - number of overrides applied</item>
/// <item><c>guard.override.expired_count</c> - number of expired overrides</item>
/// <item><c>guard.scanner.version</c> - scanner version</item>
/// <item><c>guard.scanner.confidence_threshold</c> - confidence threshold used</item>
/// </list>
/// </para>
/// </summary>
public static class AiCodeGuardSignalBinder
{
/// <summary>
/// Signal name prefix for all AI Code Guard signals.
/// </summary>
public const string SignalPrefix = "guard";
/// <summary>
/// Binds AI Code Guard evidence to a dictionary of signals.
/// </summary>
/// <param name="context">The AI Code Guard evidence context.</param>
/// <returns>A dictionary of signal names to values.</returns>
public static ImmutableDictionary<string, object?> BindToSignals(AiCodeGuardEvidenceContext context)
{
ArgumentNullException.ThrowIfNull(context);
var signals = ImmutableDictionary.CreateBuilder<string, object?>(StringComparer.Ordinal);
// Core finding signals
signals[$"{SignalPrefix}.has_finding"] = context.HasAnyFinding;
signals[$"{SignalPrefix}.has_active_finding"] = context.HasActiveFinding;
signals[$"{SignalPrefix}.count"] = context.TotalFindingCount;
signals[$"{SignalPrefix}.active_count"] = context.ActiveFindingCount;
// Severity signals
signals[$"{SignalPrefix}.severity.critical"] = context.HasFindingWithSeverity("critical");
signals[$"{SignalPrefix}.severity.high"] = context.HasFindingWithSeverity("high");
signals[$"{SignalPrefix}.severity.medium"] = context.HasFindingWithSeverity("medium");
signals[$"{SignalPrefix}.severity.low"] = context.HasFindingWithSeverity("low");
signals[$"{SignalPrefix}.severity.info"] = context.HasFindingWithSeverity("info");
// Severity counts
signals[$"{SignalPrefix}.severity.critical_count"] = context.GetFindingCountBySeverity("critical");
signals[$"{SignalPrefix}.severity.high_count"] = context.GetFindingCountBySeverity("high");
signals[$"{SignalPrefix}.severity.medium_count"] = context.GetFindingCountBySeverity("medium");
signals[$"{SignalPrefix}.severity.low_count"] = context.GetFindingCountBySeverity("low");
signals[$"{SignalPrefix}.severity.info_count"] = context.GetFindingCountBySeverity("info");
// Category signals
signals[$"{SignalPrefix}.category.ai_generated"] = context.HasFindingWithCategory("ai-generated") ||
context.HasFindingWithCategory("AiGenerated");
signals[$"{SignalPrefix}.category.insecure_pattern"] = context.HasFindingWithCategory("insecure-pattern") ||
context.HasFindingWithCategory("InsecurePattern");
signals[$"{SignalPrefix}.category.hallucination"] = context.HasFindingWithCategory("hallucination") ||
context.HasFindingWithCategory("Hallucination");
signals[$"{SignalPrefix}.category.license_risk"] = context.HasFindingWithCategory("license-risk") ||
context.HasFindingWithCategory("LicenseRisk");
signals[$"{SignalPrefix}.category.untrusted_dep"] = context.HasFindingWithCategory("untrusted-dep") ||
context.HasFindingWithCategory("UntrustedDependency");
signals[$"{SignalPrefix}.category.quality_issue"] = context.HasFindingWithCategory("quality-issue") ||
context.HasFindingWithCategory("QualityIssue");
// Category counts
signals[$"{SignalPrefix}.category.ai_generated_count"] = context.GetFindingCountByCategory("ai-generated") +
context.GetFindingCountByCategory("AiGenerated");
signals[$"{SignalPrefix}.category.insecure_pattern_count"] = context.GetFindingCountByCategory("insecure-pattern") +
context.GetFindingCountByCategory("InsecurePattern");
// Verdict signals
signals[$"{SignalPrefix}.verdict"] = context.VerdictStatus.ToString().ToLowerInvariant();
signals[$"{SignalPrefix}.verdict.pass"] = context.VerdictStatus == AiCodeGuardVerdictStatus.Pass;
signals[$"{SignalPrefix}.verdict.pass_with_warnings"] = context.VerdictStatus == AiCodeGuardVerdictStatus.PassWithWarnings;
signals[$"{SignalPrefix}.verdict.fail"] = context.VerdictStatus == AiCodeGuardVerdictStatus.Fail;
signals[$"{SignalPrefix}.verdict.error"] = context.VerdictStatus == AiCodeGuardVerdictStatus.Error;
// AI percentage
signals[$"{SignalPrefix}.ai_percentage"] = context.AiGeneratedPercentage;
// Confidence signals
signals[$"{SignalPrefix}.highest_severity"] = context.HighestSeverity;
signals[$"{SignalPrefix}.average_confidence"] = context.AverageConfidence;
signals[$"{SignalPrefix}.high_confidence_count"] = context.GetFindingCountWithConfidenceAbove(0.8);
// Override signals
signals[$"{SignalPrefix}.override.count"] = context.Overrides.Count;
signals[$"{SignalPrefix}.override.active_count"] = context.ActiveOverrideCount;
signals[$"{SignalPrefix}.override.expired_count"] = context.ExpiredOverrideCount;
// Scanner signals
var scanner = context.ScannerInfo;
if (scanner is not null)
{
signals[$"{SignalPrefix}.scanner.version"] = scanner.ScannerVersion;
signals[$"{SignalPrefix}.scanner.model_version"] = scanner.ModelVersion;
signals[$"{SignalPrefix}.scanner.confidence_threshold"] = scanner.ConfidenceThreshold;
signals[$"{SignalPrefix}.scanner.category_count"] = scanner.EnabledCategories.Count;
}
else
{
signals[$"{SignalPrefix}.scanner.version"] = null;
signals[$"{SignalPrefix}.scanner.model_version"] = null;
signals[$"{SignalPrefix}.scanner.confidence_threshold"] = null;
signals[$"{SignalPrefix}.scanner.category_count"] = 0;
}
return signals.ToImmutable();
}
/// <summary>
/// Binds AI Code Guard evidence to a nested object suitable for member access in policies.
/// This creates a hierarchical structure like:
/// guard.severity.high, guard.verdict.pass, etc.
/// </summary>
/// <param name="context">The AI Code Guard evidence context.</param>
/// <returns>A nested dictionary structure.</returns>
public static ImmutableDictionary<string, object?> BindToNestedObject(AiCodeGuardEvidenceContext context)
{
ArgumentNullException.ThrowIfNull(context);
var severity = new Dictionary<string, object?>(StringComparer.Ordinal)
{
["critical"] = context.HasFindingWithSeverity("critical"),
["high"] = context.HasFindingWithSeverity("high"),
["medium"] = context.HasFindingWithSeverity("medium"),
["low"] = context.HasFindingWithSeverity("low"),
["info"] = context.HasFindingWithSeverity("info"),
["critical_count"] = context.GetFindingCountBySeverity("critical"),
["high_count"] = context.GetFindingCountBySeverity("high"),
["medium_count"] = context.GetFindingCountBySeverity("medium"),
["low_count"] = context.GetFindingCountBySeverity("low"),
["info_count"] = context.GetFindingCountBySeverity("info"),
};
var category = new Dictionary<string, object?>(StringComparer.Ordinal)
{
["ai_generated"] = context.HasFindingWithCategory("ai-generated") ||
context.HasFindingWithCategory("AiGenerated"),
["insecure_pattern"] = context.HasFindingWithCategory("insecure-pattern") ||
context.HasFindingWithCategory("InsecurePattern"),
["hallucination"] = context.HasFindingWithCategory("hallucination"),
["license_risk"] = context.HasFindingWithCategory("license-risk") ||
context.HasFindingWithCategory("LicenseRisk"),
["untrusted_dep"] = context.HasFindingWithCategory("untrusted-dep") ||
context.HasFindingWithCategory("UntrustedDependency"),
["quality_issue"] = context.HasFindingWithCategory("quality-issue") ||
context.HasFindingWithCategory("QualityIssue"),
};
var verdict = new Dictionary<string, object?>(StringComparer.Ordinal)
{
["status"] = context.VerdictStatus.ToString().ToLowerInvariant(),
["pass"] = context.VerdictStatus == AiCodeGuardVerdictStatus.Pass,
["pass_with_warnings"] = context.VerdictStatus == AiCodeGuardVerdictStatus.PassWithWarnings,
["fail"] = context.VerdictStatus == AiCodeGuardVerdictStatus.Fail,
["error"] = context.VerdictStatus == AiCodeGuardVerdictStatus.Error,
};
var override_ = new Dictionary<string, object?>(StringComparer.Ordinal)
{
["count"] = context.Overrides.Count,
["active_count"] = context.ActiveOverrideCount,
["expired_count"] = context.ExpiredOverrideCount,
};
var scanner = context.ScannerInfo;
var scannerDict = new Dictionary<string, object?>(StringComparer.Ordinal)
{
["version"] = scanner?.ScannerVersion,
["model_version"] = scanner?.ModelVersion,
["confidence_threshold"] = scanner?.ConfidenceThreshold,
["category_count"] = scanner?.EnabledCategories.Count ?? 0,
};
return new Dictionary<string, object?>(StringComparer.Ordinal)
{
["has_finding"] = context.HasAnyFinding,
["has_active_finding"] = context.HasActiveFinding,
["count"] = context.TotalFindingCount,
["active_count"] = context.ActiveFindingCount,
["severity"] = severity,
["category"] = category,
["verdict"] = verdict,
["override"] = override_,
["scanner"] = scannerDict,
["ai_percentage"] = context.AiGeneratedPercentage,
["highest_severity"] = context.HighestSeverity,
["average_confidence"] = context.AverageConfidence,
}.ToImmutableDictionary();
}
/// <summary>
/// Maps verdict status to policy recommendation.
/// </summary>
/// <param name="context">The AI Code Guard evidence context.</param>
/// <returns>Recommendation string (allow, review, block).</returns>
public static string GetRecommendation(AiCodeGuardEvidenceContext context)
{
ArgumentNullException.ThrowIfNull(context);
return context.VerdictStatus switch
{
AiCodeGuardVerdictStatus.Pass => "allow",
AiCodeGuardVerdictStatus.PassWithWarnings => "review",
AiCodeGuardVerdictStatus.Fail => "block",
AiCodeGuardVerdictStatus.Error => "block",
_ => "review"
};
}
/// <summary>
/// Creates finding summary for policy explanation (deterministic, ASCII-only).
/// </summary>
/// <param name="context">The AI Code Guard evidence context.</param>
/// <returns>A summary string for audit/explanation purposes.</returns>
public static string CreateFindingSummary(AiCodeGuardEvidenceContext context)
{
ArgumentNullException.ThrowIfNull(context);
if (!context.HasAnyFinding)
{
return "No AI code guard findings detected.";
}
var findings = context.ActiveFindings;
var severityCounts = findings
.GroupBy(f => f.Severity, StringComparer.OrdinalIgnoreCase)
.ToDictionary(g => g.Key.ToLowerInvariant(), g => g.Count(), StringComparer.Ordinal);
var parts = new List<string>();
if (severityCounts.TryGetValue("critical", out var critical) && critical > 0)
{
parts.Add($"{critical} critical");
}
if (severityCounts.TryGetValue("high", out var high) && high > 0)
{
parts.Add($"{high} high");
}
if (severityCounts.TryGetValue("medium", out var medium) && medium > 0)
{
parts.Add($"{medium} medium");
}
if (severityCounts.TryGetValue("low", out var low) && low > 0)
{
parts.Add($"{low} low");
}
if (severityCounts.TryGetValue("info", out var info) && info > 0)
{
parts.Add($"{info} info");
}
var summary = string.Format(
CultureInfo.InvariantCulture,
"{0} AI code guard finding(s): {1}",
findings.Count,
string.Join(", ", parts));
if (context.AiGeneratedPercentage.HasValue)
{
summary += string.Format(
CultureInfo.InvariantCulture,
" (AI-generated: {0:F1}%)",
context.AiGeneratedPercentage.Value);
}
return summary;
}
/// <summary>
/// Creates explain trace annotation for policy decisions.
/// </summary>
/// <param name="context">The AI Code Guard evidence context.</param>
/// <returns>Deterministic trace annotation.</returns>
public static string CreateExplainTrace(AiCodeGuardEvidenceContext context)
{
ArgumentNullException.ThrowIfNull(context);
var lines = new List<string>
{
$"guard.verdict={context.VerdictStatus}",
$"guard.total_findings={context.TotalFindingCount}",
$"guard.active_findings={context.ActiveFindingCount}",
$"guard.overrides={context.Overrides.Count}"
};
if (context.AiGeneratedPercentage.HasValue)
{
lines.Add($"guard.ai_percentage={context.AiGeneratedPercentage.Value:F1}");
}
if (context.HighestSeverity is not null)
{
lines.Add($"guard.highest_severity={context.HighestSeverity}");
}
// Sort for determinism
lines.Sort(StringComparer.Ordinal);
return string.Join(";", lines);
}
}

View File

@@ -0,0 +1,176 @@
// -----------------------------------------------------------------------------
// IAiCodeGuardEvidenceProvider.cs
// Sprint: SPRINT_20260112_010_POLICY_ai_code_guard_policy
// Task: POLICY-AIGUARD-001 - AI Code Guard evidence provider interface
// -----------------------------------------------------------------------------
using System.Collections.Immutable;
namespace StellaOps.Policy.AiCodeGuard;
/// <summary>
/// Provides AI Code Guard evidence for policy evaluation.
/// </summary>
public interface IAiCodeGuardEvidenceProvider
{
/// <summary>
/// Gets all AI Code Guard findings.
/// </summary>
ImmutableList<AiCodeGuardFinding> Findings { get; }
/// <summary>
/// Gets all policy overrides applied to findings.
/// </summary>
ImmutableList<AiCodeGuardOverrideRecord> Overrides { get; }
/// <summary>
/// Gets the overall verdict status.
/// </summary>
AiCodeGuardVerdictStatus VerdictStatus { get; }
/// <summary>
/// Gets the estimated AI-generated code percentage (0-100).
/// </summary>
double? AiGeneratedPercentage { get; }
/// <summary>
/// Gets the scanner configuration used.
/// </summary>
AiCodeGuardScannerInfo? ScannerInfo { get; }
}
/// <summary>
/// AI Code Guard finding from analysis.
/// </summary>
public sealed record AiCodeGuardFinding
{
/// <summary>
/// Unique finding identifier.
/// </summary>
public required string Id { get; init; }
/// <summary>
/// Finding category (e.g., "ai-generated", "insecure-pattern", "hallucination").
/// </summary>
public required string Category { get; init; }
/// <summary>
/// Finding severity (info, low, medium, high, critical).
/// </summary>
public required string Severity { get; init; }
/// <summary>
/// Detection confidence (0.0-1.0).
/// </summary>
public required double Confidence { get; init; }
/// <summary>
/// Rule ID that triggered this finding.
/// </summary>
public required string RuleId { get; init; }
/// <summary>
/// File path where finding was detected.
/// </summary>
public required string FilePath { get; init; }
/// <summary>
/// Start line number (1-based).
/// </summary>
public required int StartLine { get; init; }
/// <summary>
/// End line number (1-based).
/// </summary>
public required int EndLine { get; init; }
/// <summary>
/// Human-readable description.
/// </summary>
public string? Description { get; init; }
/// <summary>
/// Suggested remediation.
/// </summary>
public string? Remediation { get; init; }
}
/// <summary>
/// AI Code Guard override record.
/// </summary>
public sealed record AiCodeGuardOverrideRecord
{
/// <summary>
/// Finding ID being overridden.
/// </summary>
public required string FindingId { get; init; }
/// <summary>
/// Override action (suppress, downgrade, accept-risk, false-positive).
/// </summary>
public required string Action { get; init; }
/// <summary>
/// Justification for the override.
/// </summary>
public required string Justification { get; init; }
/// <summary>
/// Who approved the override.
/// </summary>
public required string ApprovedBy { get; init; }
/// <summary>
/// When the override was approved.
/// </summary>
public required DateTimeOffset ApprovedAt { get; init; }
/// <summary>
/// When the override expires (optional).
/// </summary>
public DateTimeOffset? ExpiresAt { get; init; }
}
/// <summary>
/// Overall verdict status.
/// </summary>
public enum AiCodeGuardVerdictStatus
{
/// <summary>Analysis passed.</summary>
Pass,
/// <summary>Analysis passed with warnings.</summary>
PassWithWarnings,
/// <summary>Analysis failed.</summary>
Fail,
/// <summary>Analysis errored.</summary>
Error
}
/// <summary>
/// Scanner configuration information.
/// </summary>
public sealed record AiCodeGuardScannerInfo
{
/// <summary>
/// Scanner version.
/// </summary>
public required string ScannerVersion { get; init; }
/// <summary>
/// Detection model version.
/// </summary>
public required string ModelVersion { get; init; }
/// <summary>
/// Confidence threshold used.
/// </summary>
public required double ConfidenceThreshold { get; init; }
/// <summary>
/// Enabled detection categories.
/// </summary>
public required ImmutableList<string> EnabledCategories { get; init; }
}