Gaps fill up, fixes, ui restructuring

This commit is contained in:
master
2026-02-19 22:10:54 +02:00
parent b5829dce5c
commit 04cacdca8a
331 changed files with 42859 additions and 2174 deletions

View File

@@ -15,6 +15,7 @@ using StellaOps.Policy.Engine.Events;
using StellaOps.Policy.Engine.ExceptionCache;
using StellaOps.Policy.Engine.Gates;
using StellaOps.Policy.Engine.Options;
using StellaOps.Policy.Gates;
using StellaOps.Policy.Engine.ReachabilityFacts;
using StellaOps.Policy.Engine.Scoring.EvidenceWeightedScore;
using StellaOps.Policy.Engine.Services;
@@ -232,6 +233,28 @@ public static class PolicyEngineServiceCollectionExtensions
return services.AddPolicyDecisionAttestation();
}
/// <summary>
/// Adds the execution evidence policy gate.
/// Enforces that artifacts have signed execution evidence before promotion.
/// Sprint: SPRINT_20260219_013 (SEE-03)
/// </summary>
public static IServiceCollection AddExecutionEvidenceGate(this IServiceCollection services)
{
services.TryAddSingleton<IContextPolicyGate, ExecutionEvidenceGate>();
return services;
}
/// <summary>
/// Adds the beacon rate policy gate.
/// Enforces minimum beacon verification rate for runtime canary coverage.
/// Sprint: SPRINT_20260219_014 (BEA-03)
/// </summary>
public static IServiceCollection AddBeaconRateGate(this IServiceCollection services)
{
services.TryAddSingleton<IContextPolicyGate, BeaconRateGate>();
return services;
}
/// <summary>
/// Adds build gate evaluators for exception recheck policies.
/// </summary>
@@ -328,6 +351,11 @@ public static class PolicyEngineServiceCollectionExtensions
// Always registered; activation controlled by PolicyEvidenceWeightedScoreOptions.Enabled
services.AddEvidenceWeightedScore();
// Execution evidence and beacon rate gates (Sprint: SPRINT_20260219_013/014)
// Always registered; activation controlled by PolicyGateOptions.ExecutionEvidence.Enabled / BeaconRate.Enabled
services.AddExecutionEvidenceGate();
services.AddBeaconRateGate();
return services;
}

View File

@@ -0,0 +1,115 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Policy.Gates;
using System.Collections.Immutable;
using System.Globalization;
namespace StellaOps.Policy.Engine.Gates;
/// <summary>
/// Policy gate that enforces beacon verification rate thresholds.
/// When enabled, blocks or warns for releases where beacon coverage is insufficient.
/// Sprint: SPRINT_20260219_014 (BEA-03)
/// </summary>
public sealed class BeaconRateGate : IContextPolicyGate
{
private readonly IOptionsMonitor<PolicyGateOptions> _options;
private readonly ILogger<BeaconRateGate> _logger;
private readonly TimeProvider _timeProvider;
public string Id => "beacon-rate";
public string DisplayName => "Beacon Verification Rate";
public string Description => "Enforces minimum beacon verification rate for runtime canary coverage.";
public BeaconRateGate(
IOptionsMonitor<PolicyGateOptions> options,
ILogger<BeaconRateGate> logger,
TimeProvider? timeProvider = null)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_timeProvider = timeProvider ?? TimeProvider.System;
}
public Task<GateResult> EvaluateAsync(PolicyGateContext context, CancellationToken ct = default)
{
var gateOpts = _options.CurrentValue.BeaconRate;
if (!gateOpts.Enabled)
{
return Task.FromResult(GateResult.Pass(Id, "Beacon rate gate is disabled"));
}
var environment = context.Environment ?? "unknown";
// Check if environment requires beacon coverage.
if (!gateOpts.RequiredEnvironments.Contains(environment, StringComparer.OrdinalIgnoreCase))
{
return Task.FromResult(GateResult.Pass(Id, $"Beacon rate not required for environment '{environment}'"));
}
// Read beacon data from context metadata.
double verificationRate = 0;
var hasBeaconData = context.Metadata?.TryGetValue("beacon_verification_rate", out var rateStr) == true
&& double.TryParse(rateStr, CultureInfo.InvariantCulture, out verificationRate);
if (!hasBeaconData)
{
_logger.LogInformation(
"BeaconRateGate: no beacon data for environment {Environment}, subject {Subject}",
environment, context.SubjectKey);
return gateOpts.MissingBeaconAction == PolicyGateDecisionType.Block
? Task.FromResult(GateResult.Fail(Id, "No beacon telemetry data available for this artifact",
ImmutableDictionary<string, object>.Empty
.Add("environment", environment)
.Add("suggestion", "Ensure beacon instrumentation is active in the target environment")))
: Task.FromResult(GateResult.Pass(Id, "No beacon data available (warn mode)",
new[] { "No beacon telemetry found; ensure runtime canary beacons are deployed" }));
}
// Check minimum beacon count before enforcing rate.
int beaconCount = 0;
var hasBeaconCount = context.Metadata?.TryGetValue("beacon_verified_count", out var countStr) == true
&& int.TryParse(countStr, out beaconCount);
if (hasBeaconCount && beaconCount < gateOpts.MinBeaconCount)
{
_logger.LogDebug(
"BeaconRateGate: insufficient beacon count ({Count} < {Min}) for {Environment}; skipping rate check",
beaconCount, gateOpts.MinBeaconCount, environment);
return Task.FromResult(GateResult.Pass(Id,
$"Beacon count ({beaconCount}) below minimum ({gateOpts.MinBeaconCount}); rate enforcement deferred",
new[] { $"Only {beaconCount} beacons observed; need {gateOpts.MinBeaconCount} before rate enforcement applies" }));
}
// Evaluate rate against threshold.
if (verificationRate < gateOpts.MinVerificationRate)
{
_logger.LogInformation(
"BeaconRateGate: rate {Rate:F4} below threshold {Threshold:F4} for {Environment}, subject {Subject}",
verificationRate, gateOpts.MinVerificationRate, environment, context.SubjectKey);
var details = ImmutableDictionary<string, object>.Empty
.Add("verification_rate", verificationRate)
.Add("threshold", gateOpts.MinVerificationRate)
.Add("environment", environment);
return gateOpts.BelowThresholdAction == PolicyGateDecisionType.Block
? Task.FromResult(GateResult.Fail(Id,
$"Beacon verification rate ({verificationRate:P1}) is below threshold ({gateOpts.MinVerificationRate:P1})",
details.Add("suggestion", "Investigate beacon gaps; possible deployment drift or instrumentation failure")))
: Task.FromResult(GateResult.Pass(Id,
$"Beacon verification rate ({verificationRate:P1}) is below threshold (warn mode)",
new[] { $"Beacon rate {verificationRate:P1} < {gateOpts.MinVerificationRate:P1}; investigate potential gaps" }));
}
_logger.LogDebug(
"BeaconRateGate: passed for {Environment}, rate={Rate:F4}, subject {Subject}",
environment, verificationRate, context.SubjectKey);
return Task.FromResult(GateResult.Pass(Id,
$"Beacon verification rate ({verificationRate:P1}) meets threshold ({gateOpts.MinVerificationRate:P1})"));
}
}

View File

@@ -0,0 +1,99 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Policy.Gates;
using System.Collections.Immutable;
namespace StellaOps.Policy.Engine.Gates;
/// <summary>
/// Policy gate that enforces execution evidence requirements.
/// When enabled, blocks or warns for releases without signed execution evidence.
/// Sprint: SPRINT_20260219_013 (SEE-03)
/// </summary>
public sealed class ExecutionEvidenceGate : IContextPolicyGate
{
private readonly IOptionsMonitor<PolicyGateOptions> _options;
private readonly ILogger<ExecutionEvidenceGate> _logger;
private readonly TimeProvider _timeProvider;
public string Id => "execution-evidence";
public string DisplayName => "Execution Evidence";
public string Description => "Requires signed execution evidence (runtime trace attestation) for production releases.";
public ExecutionEvidenceGate(
IOptionsMonitor<PolicyGateOptions> options,
ILogger<ExecutionEvidenceGate> logger,
TimeProvider? timeProvider = null)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_timeProvider = timeProvider ?? TimeProvider.System;
}
public Task<GateResult> EvaluateAsync(PolicyGateContext context, CancellationToken ct = default)
{
var gateOpts = _options.CurrentValue.ExecutionEvidence;
if (!gateOpts.Enabled)
{
return Task.FromResult(GateResult.Pass(Id, "Execution evidence gate is disabled"));
}
var environment = context.Environment ?? "unknown";
// Check if environment requires execution evidence.
if (!gateOpts.RequiredEnvironments.Contains(environment, StringComparer.OrdinalIgnoreCase))
{
return Task.FromResult(GateResult.Pass(Id, $"Execution evidence not required for environment '{environment}'"));
}
// Check metadata for execution evidence fields.
var hasEvidence = context.Metadata?.TryGetValue("has_execution_evidence", out var evidenceStr) == true
&& string.Equals(evidenceStr, "true", StringComparison.OrdinalIgnoreCase);
if (!hasEvidence)
{
_logger.LogInformation(
"ExecutionEvidenceGate: missing execution evidence for environment {Environment}, subject {Subject}",
environment, context.SubjectKey);
return gateOpts.MissingEvidenceAction == PolicyGateDecisionType.Block
? Task.FromResult(GateResult.Fail(Id, "Signed execution evidence is required for production releases",
ImmutableDictionary<string, object>.Empty
.Add("environment", environment)
.Add("suggestion", "Run the execution evidence pipeline before promoting to production")))
: Task.FromResult(GateResult.Pass(Id, "Execution evidence missing (warn mode)",
new[] { "No signed execution evidence found; consider running the trace pipeline" }));
}
// Validate evidence quality if hot symbol count is provided.
if (context.Metadata?.TryGetValue("execution_evidence_hot_symbol_count", out var hotStr) == true
&& int.TryParse(hotStr, out var hotCount)
&& hotCount < gateOpts.MinHotSymbolCount)
{
_logger.LogInformation(
"ExecutionEvidenceGate: insufficient hot symbols ({Count} < {Min}) for environment {Environment}",
hotCount, gateOpts.MinHotSymbolCount, environment);
return Task.FromResult(GateResult.Pass(Id,
$"Execution evidence has insufficient coverage ({hotCount} hot symbols < {gateOpts.MinHotSymbolCount} minimum)",
new[] { $"Execution evidence trace has only {hotCount} hot symbols; minimum is {gateOpts.MinHotSymbolCount}" }));
}
// Validate unique call paths if provided.
if (context.Metadata?.TryGetValue("execution_evidence_unique_call_paths", out var pathStr) == true
&& int.TryParse(pathStr, out var pathCount)
&& pathCount < gateOpts.MinUniqueCallPaths)
{
return Task.FromResult(GateResult.Pass(Id,
$"Execution evidence has insufficient call path coverage ({pathCount} < {gateOpts.MinUniqueCallPaths})",
new[] { $"Execution evidence covers only {pathCount} unique call paths; minimum is {gateOpts.MinUniqueCallPaths}" }));
}
_logger.LogDebug(
"ExecutionEvidenceGate: passed for environment {Environment}, subject {Subject}",
environment, context.SubjectKey);
return Task.FromResult(GateResult.Pass(Id, "Signed execution evidence is present and meets quality thresholds"));
}
}

View File

@@ -151,6 +151,34 @@ public sealed record PolicyGateEvidence
/// </summary>
[JsonPropertyName("pathLength")]
public int? PathLength { get; init; }
/// <summary>
/// Whether signed execution evidence exists for this artifact.
/// Sprint: SPRINT_20260219_013 (SEE-03)
/// </summary>
[JsonPropertyName("hasExecutionEvidence")]
public bool HasExecutionEvidence { get; init; }
/// <summary>
/// Execution evidence predicate digest (sha256:...), if available.
/// Sprint: SPRINT_20260219_013 (SEE-03)
/// </summary>
[JsonPropertyName("executionEvidenceDigest")]
public string? ExecutionEvidenceDigest { get; init; }
/// <summary>
/// Beacon verification rate (0.0 - 1.0), if beacons are observed.
/// Sprint: SPRINT_20260219_014 (BEA-03)
/// </summary>
[JsonPropertyName("beaconVerificationRate")]
public double? BeaconVerificationRate { get; init; }
/// <summary>
/// Total beacons verified in the lookback window.
/// Sprint: SPRINT_20260219_014 (BEA-03)
/// </summary>
[JsonPropertyName("beaconCount")]
public int? BeaconCount { get; init; }
}
/// <summary>
@@ -366,4 +394,45 @@ public sealed record PolicyGateRequest
/// Signature verification method (dsse, cosign, pgp, x509).
/// </summary>
public string? VexSignatureMethod { get; init; }
// Execution evidence fields (added for ExecutionEvidenceGate integration)
// Sprint: SPRINT_20260219_013 (SEE-03)
/// <summary>
/// Whether signed execution evidence exists for this artifact.
/// </summary>
public bool HasExecutionEvidence { get; init; }
/// <summary>
/// Execution evidence predicate digest (sha256:...).
/// </summary>
public string? ExecutionEvidenceDigest { get; init; }
/// <summary>
/// Number of hot symbols in the execution evidence trace summary.
/// </summary>
public int? ExecutionEvidenceHotSymbolCount { get; init; }
/// <summary>
/// Number of unique call paths in the execution evidence.
/// </summary>
public int? ExecutionEvidenceUniqueCallPaths { get; init; }
// Beacon attestation fields (added for BeaconRateGate integration)
// Sprint: SPRINT_20260219_014 (BEA-03)
/// <summary>
/// Beacon verification rate (0.0 - 1.0) for this artifact/environment.
/// </summary>
public double? BeaconVerificationRate { get; init; }
/// <summary>
/// Total beacons verified in the lookback window.
/// </summary>
public int? BeaconVerifiedCount { get; init; }
/// <summary>
/// Total beacons expected in the lookback window.
/// </summary>
public int? BeaconExpectedCount { get; init; }
}

View File

@@ -43,6 +43,18 @@ public sealed class PolicyGateOptions
/// </summary>
public FacetQuotaGateOptions FacetQuota { get; set; } = new();
/// <summary>
/// Execution evidence gate options.
/// Sprint: SPRINT_20260219_013 (SEE-03)
/// </summary>
public ExecutionEvidenceGateOptions ExecutionEvidence { get; set; } = new();
/// <summary>
/// Beacon verification rate gate options.
/// Sprint: SPRINT_20260219_014 (BEA-03)
/// </summary>
public BeaconRateGateOptions BeaconRate { get; set; } = new();
/// <summary>
/// Whether gates are enabled.
/// </summary>
@@ -216,3 +228,80 @@ public sealed class FacetQuotaOverride
/// </summary>
public List<string> AllowlistGlobs { get; set; } = new();
}
/// <summary>
/// Configuration options for the execution evidence gate.
/// When enabled, requires signed execution evidence for production releases.
/// Sprint: SPRINT_20260219_013 (SEE-03)
/// </summary>
public sealed class ExecutionEvidenceGateOptions
{
/// <summary>
/// Whether execution evidence enforcement is enabled.
/// </summary>
public bool Enabled { get; set; } = false;
/// <summary>
/// Action when execution evidence is missing.
/// </summary>
public PolicyGateDecisionType MissingEvidenceAction { get; set; } = PolicyGateDecisionType.Warn;
/// <summary>
/// Environments where execution evidence is required (case-insensitive).
/// Default: production only.
/// </summary>
public List<string> RequiredEnvironments { get; set; } = new() { "production" };
/// <summary>
/// Minimum number of hot symbols to consider evidence meaningful.
/// Prevents trivial evidence from satisfying the gate.
/// </summary>
public int MinHotSymbolCount { get; set; } = 3;
/// <summary>
/// Minimum number of unique call paths to consider evidence meaningful.
/// </summary>
public int MinUniqueCallPaths { get; set; } = 1;
}
/// <summary>
/// Configuration options for the beacon verification rate gate.
/// When enabled, enforces minimum beacon coverage thresholds.
/// Sprint: SPRINT_20260219_014 (BEA-03)
/// </summary>
public sealed class BeaconRateGateOptions
{
/// <summary>
/// Whether beacon rate enforcement is enabled.
/// </summary>
public bool Enabled { get; set; } = false;
/// <summary>
/// Action when beacon rate is below threshold.
/// </summary>
public PolicyGateDecisionType BelowThresholdAction { get; set; } = PolicyGateDecisionType.Warn;
/// <summary>
/// Action when no beacon data exists for the artifact.
/// </summary>
public PolicyGateDecisionType MissingBeaconAction { get; set; } = PolicyGateDecisionType.Warn;
/// <summary>
/// Minimum beacon verification rate (0.0 - 1.0).
/// Beacon rate below this triggers the BelowThresholdAction.
/// Default: 0.8 (80% of expected beacons must be observed).
/// </summary>
public double MinVerificationRate { get; set; } = 0.8;
/// <summary>
/// Environments where beacon rate is enforced (case-insensitive).
/// Default: production only.
/// </summary>
public List<string> RequiredEnvironments { get; set; } = new() { "production" };
/// <summary>
/// Minimum number of beacons observed before rate enforcement applies.
/// Prevents premature gating on insufficient data.
/// </summary>
public int MinBeaconCount { get; set; } = 10;
}