Add signal contracts for reachability, exploitability, trust, and unknown symbols
- Introduced `ReachabilityState`, `RuntimeHit`, `ExploitabilitySignal`, `ReachabilitySignal`, `SignalEnvelope`, `SignalType`, `TrustSignal`, and `UnknownSymbolSignal` records to define various signal types and their properties. - Implemented JSON serialization attributes for proper data interchange. - Created project files for the new signal contracts library and corresponding test projects. - Added deterministic test fixtures for micro-interaction testing. - Included cryptographic keys for secure operations with cosign.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using StellaOps.Policy.Engine.ConsoleSurface;
|
||||
using StellaOps.Policy.Engine.Options;
|
||||
|
||||
namespace StellaOps.Policy.Engine.Endpoints;
|
||||
|
||||
@@ -8,6 +9,7 @@ internal static class ConsoleSimulationEndpoint
|
||||
public static IEndpointRouteBuilder MapConsoleSimulationDiff(this IEndpointRouteBuilder routes)
|
||||
{
|
||||
routes.MapPost("/policy/console/simulations/diff", HandleAsync)
|
||||
.RequireRateLimiting(PolicyEngineRateLimitOptions.PolicyName)
|
||||
.WithName("PolicyEngine.ConsoleSimulationDiff")
|
||||
.Produces<ConsoleSimulationDiffResponse>(StatusCodes.Status200OK)
|
||||
.ProducesValidationProblem();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using StellaOps.Policy.Engine.Options;
|
||||
using StellaOps.Policy.Engine.Overlay;
|
||||
|
||||
namespace StellaOps.Policy.Engine.Endpoints;
|
||||
@@ -8,6 +9,7 @@ public static class OverlaySimulationEndpoint
|
||||
public static IEndpointRouteBuilder MapOverlaySimulation(this IEndpointRouteBuilder routes)
|
||||
{
|
||||
routes.MapPost("/simulation/overlay", HandleAsync)
|
||||
.RequireRateLimiting(PolicyEngineRateLimitOptions.PolicyName)
|
||||
.WithName("PolicyEngine.OverlaySimulation");
|
||||
|
||||
return routes;
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Text;
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using StellaOps.Policy.Engine.Options;
|
||||
using StellaOps.Policy.Engine.Streaming;
|
||||
using StellaOps.Policy.Engine.Overlay;
|
||||
|
||||
@@ -12,6 +13,7 @@ public static class PathScopeSimulationEndpoint
|
||||
public static IEndpointRouteBuilder MapPathScopeSimulation(this IEndpointRouteBuilder routes)
|
||||
{
|
||||
routes.MapPost("/simulation/path-scope", HandleAsync)
|
||||
.RequireRateLimiting(PolicyEngineRateLimitOptions.PolicyName)
|
||||
.WithName("PolicyEngine.PathScopeSimulation");
|
||||
|
||||
return routes;
|
||||
|
||||
@@ -82,6 +82,12 @@ internal static class RiskProfileEndpoints
|
||||
.Produces<RiskProfileHashResponse>(StatusCodes.Status200OK)
|
||||
.Produces<ProblemHttpResult>(StatusCodes.Status404NotFound);
|
||||
|
||||
group.MapGet("/{profileId}/metadata", GetProfileMetadata)
|
||||
.WithName("GetRiskProfileMetadata")
|
||||
.WithSummary("Export risk profile metadata for notification enrichment (POLICY-RISK-40-002).")
|
||||
.Produces<RiskProfileMetadataExportResponse>(StatusCodes.Status200OK)
|
||||
.Produces<ProblemHttpResult>(StatusCodes.Status404NotFound);
|
||||
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
@@ -461,6 +467,53 @@ internal static class RiskProfileEndpoints
|
||||
return Results.Ok(new RiskProfileHashResponse(profile.Id, profile.Version, hash, contentOnly));
|
||||
}
|
||||
|
||||
private static IResult GetProfileMetadata(
|
||||
HttpContext context,
|
||||
[FromRoute] string profileId,
|
||||
RiskProfileConfigurationService profileService,
|
||||
RiskProfileLifecycleService lifecycleService)
|
||||
{
|
||||
var scopeResult = ScopeAuthorization.RequireScope(context, StellaOpsScopes.PolicyRead);
|
||||
if (scopeResult is not null)
|
||||
{
|
||||
return scopeResult;
|
||||
}
|
||||
|
||||
var profile = profileService.GetProfile(profileId);
|
||||
if (profile == null)
|
||||
{
|
||||
return Results.NotFound(new ProblemDetails
|
||||
{
|
||||
Title = "Profile not found",
|
||||
Detail = $"Risk profile '{profileId}' was not found.",
|
||||
Status = StatusCodes.Status404NotFound
|
||||
});
|
||||
}
|
||||
|
||||
var versions = lifecycleService.GetAllVersions(profileId);
|
||||
var activeVersion = versions.FirstOrDefault(v => v.Status == RiskProfileLifecycleStatus.Active);
|
||||
var hash = profileService.ComputeHash(profile);
|
||||
|
||||
// Extract signal names and severity thresholds for notification context
|
||||
var signalNames = profile.Signals.Select(s => s.Name).ToList();
|
||||
var severityThresholds = profile.Overrides.Severity
|
||||
.Select(s => new SeverityThresholdInfo(s.Set.ToString(), s.When))
|
||||
.ToList();
|
||||
|
||||
return Results.Ok(new RiskProfileMetadataExportResponse(
|
||||
ProfileId: profile.Id,
|
||||
Version: profile.Version,
|
||||
Description: profile.Description,
|
||||
Hash: hash,
|
||||
Status: activeVersion?.Status.ToString() ?? "unknown",
|
||||
SignalNames: signalNames,
|
||||
SeverityThresholds: severityThresholds,
|
||||
CustomMetadata: profile.Metadata,
|
||||
ExtendsProfile: profile.Extends,
|
||||
ExportedAt: DateTime.UtcNow
|
||||
));
|
||||
}
|
||||
|
||||
private static string? ResolveActorId(HttpContext context)
|
||||
{
|
||||
var user = context.User;
|
||||
@@ -521,4 +574,26 @@ internal sealed record CompareRiskProfilesRequest(
|
||||
string ToProfileId,
|
||||
string ToVersion);
|
||||
|
||||
/// <summary>
|
||||
/// Metadata export response for notification enrichment (POLICY-RISK-40-002).
|
||||
/// </summary>
|
||||
internal sealed record RiskProfileMetadataExportResponse(
|
||||
string ProfileId,
|
||||
string Version,
|
||||
string? Description,
|
||||
string Hash,
|
||||
string Status,
|
||||
IReadOnlyList<string> SignalNames,
|
||||
IReadOnlyList<SeverityThresholdInfo> SeverityThresholds,
|
||||
Dictionary<string, object?>? CustomMetadata,
|
||||
string? ExtendsProfile,
|
||||
DateTime ExportedAt);
|
||||
|
||||
/// <summary>
|
||||
/// Severity threshold information for notification context.
|
||||
/// </summary>
|
||||
internal sealed record SeverityThresholdInfo(
|
||||
string TargetSeverity,
|
||||
Dictionary<string, object> WhenConditions);
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using StellaOps.Auth.Abstractions;
|
||||
using StellaOps.Policy.Engine.Options;
|
||||
using StellaOps.Policy.Engine.Services;
|
||||
using StellaOps.Policy.Engine.Simulation;
|
||||
|
||||
@@ -12,6 +13,7 @@ internal static class RiskSimulationEndpoints
|
||||
{
|
||||
var group = endpoints.MapGroup("/api/risk/simulation")
|
||||
.RequireAuthorization()
|
||||
.RequireRateLimiting(PolicyEngineRateLimitOptions.PolicyName)
|
||||
.WithTags("Risk Simulation");
|
||||
|
||||
group.MapPost("/", RunSimulation)
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
namespace StellaOps.Policy.Engine.Options;
|
||||
|
||||
/// <summary>
|
||||
/// Rate limiting configuration for Policy Engine simulation endpoints.
|
||||
/// </summary>
|
||||
public sealed class PolicyEngineRateLimitOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration section name for binding.
|
||||
/// </summary>
|
||||
public const string SectionName = "RateLimiting";
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of permits per window for simulation endpoints.
|
||||
/// Default: 100 requests per window.
|
||||
/// </summary>
|
||||
public int SimulationPermitLimit { get; set; } = 100;
|
||||
|
||||
/// <summary>
|
||||
/// Window duration in seconds for rate limiting.
|
||||
/// Default: 60 seconds.
|
||||
/// </summary>
|
||||
public int WindowSeconds { get; set; } = 60;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of requests that can be queued when the limit is reached.
|
||||
/// Default: 10 requests.
|
||||
/// </summary>
|
||||
public int QueueLimit { get; set; } = 10;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to partition rate limits by tenant ID.
|
||||
/// When enabled, each tenant gets their own rate limit bucket.
|
||||
/// Default: true.
|
||||
/// </summary>
|
||||
public bool TenantPartitioning { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether rate limiting is enabled.
|
||||
/// Default: true.
|
||||
/// </summary>
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Custom policy name for the simulation rate limiter.
|
||||
/// </summary>
|
||||
public const string PolicyName = "policy-simulation";
|
||||
}
|
||||
@@ -1,27 +1,29 @@
|
||||
using System.IO;
|
||||
using System.Threading.RateLimiting;
|
||||
using Microsoft.AspNetCore.RateLimiting;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NetEscapades.Configuration.Yaml;
|
||||
using StellaOps.Auth.Abstractions;
|
||||
using StellaOps.Auth.Client;
|
||||
using StellaOps.Auth.ServerIntegration;
|
||||
using StellaOps.Configuration;
|
||||
using StellaOps.Policy.Engine.Hosting;
|
||||
using StellaOps.Policy.Engine.Options;
|
||||
using StellaOps.Policy.Engine.Compilation;
|
||||
using StellaOps.Policy.Engine.Endpoints;
|
||||
using StellaOps.Policy.Engine.BatchEvaluation;
|
||||
using StellaOps.Policy.Engine.DependencyInjection;
|
||||
using StellaOps.PolicyDsl;
|
||||
using StellaOps.Policy.Engine.Services;
|
||||
using StellaOps.Policy.Engine.Workers;
|
||||
using StellaOps.Policy.Engine.Streaming;
|
||||
using StellaOps.Policy.Engine.Telemetry;
|
||||
using StellaOps.Policy.Engine.ConsoleSurface;
|
||||
using StellaOps.AirGap.Policy;
|
||||
using StellaOps.Policy.Engine.Orchestration;
|
||||
using StellaOps.Policy.Engine.ReachabilityFacts;
|
||||
using StellaOps.Policy.Engine.Storage.InMemory;
|
||||
using StellaOps.Policy.Engine.Storage.Mongo.Repositories;
|
||||
using StellaOps.Policy.Engine.Hosting;
|
||||
using StellaOps.Policy.Engine.Options;
|
||||
using StellaOps.Policy.Engine.Compilation;
|
||||
using StellaOps.Policy.Engine.Endpoints;
|
||||
using StellaOps.Policy.Engine.BatchEvaluation;
|
||||
using StellaOps.Policy.Engine.DependencyInjection;
|
||||
using StellaOps.PolicyDsl;
|
||||
using StellaOps.Policy.Engine.Services;
|
||||
using StellaOps.Policy.Engine.Workers;
|
||||
using StellaOps.Policy.Engine.Streaming;
|
||||
using StellaOps.Policy.Engine.Telemetry;
|
||||
using StellaOps.Policy.Engine.ConsoleSurface;
|
||||
using StellaOps.AirGap.Policy;
|
||||
using StellaOps.Policy.Engine.Orchestration;
|
||||
using StellaOps.Policy.Engine.ReachabilityFacts;
|
||||
using StellaOps.Policy.Engine.Storage.InMemory;
|
||||
using StellaOps.Policy.Engine.Storage.Mongo.Repositories;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
@@ -113,10 +115,10 @@ builder.Services.AddOptions<PolicyEngineOptions>()
|
||||
})
|
||||
.ValidateOnStart();
|
||||
|
||||
builder.Services.AddSingleton(sp => sp.GetRequiredService<IOptions<PolicyEngineOptions>>().Value);
|
||||
builder.Services.AddSingleton(sp => sp.GetRequiredService<PolicyEngineOptions>().ExceptionLifecycle);
|
||||
builder.Services.AddSingleton(TimeProvider.System);
|
||||
builder.Services.AddSingleton<PolicyEngineStartupDiagnostics>();
|
||||
builder.Services.AddSingleton(sp => sp.GetRequiredService<IOptions<PolicyEngineOptions>>().Value);
|
||||
builder.Services.AddSingleton(sp => sp.GetRequiredService<PolicyEngineOptions>().ExceptionLifecycle);
|
||||
builder.Services.AddSingleton(TimeProvider.System);
|
||||
builder.Services.AddSingleton<PolicyEngineStartupDiagnostics>();
|
||||
builder.Services.AddSingleton<PolicyTimelineEvents>();
|
||||
builder.Services.AddSingleton<EvidenceBundleService>();
|
||||
builder.Services.AddSingleton<PolicyEvaluationAttestationService>();
|
||||
@@ -125,63 +127,102 @@ builder.Services.AddSingleton<RiskProfileConfigurationService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.RiskProfile.Lifecycle.RiskProfileLifecycleService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.RiskProfile.Scope.ScopeAttachmentService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.RiskProfile.Overrides.OverrideService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Scoring.IRiskScoringJobStore, StellaOps.Policy.Engine.Scoring.InMemoryRiskScoringJobStore>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Scoring.RiskScoringTriggerService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Simulation.RiskSimulationService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Signals.Entropy.EntropyPenaltyCalculator>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.RiskProfile.Export.ProfileExportService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Events.ProfileEventPublisher>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Events.IExceptionEventPublisher>(sp =>
|
||||
new StellaOps.Policy.Engine.Events.LoggingExceptionEventPublisher(
|
||||
sp.GetService<StellaOps.Policy.Engine.ExceptionCache.IExceptionEffectiveCache>(),
|
||||
sp.GetRequiredService<ILogger<StellaOps.Policy.Engine.Events.LoggingExceptionEventPublisher>>()));
|
||||
builder.Services.AddSingleton<ExceptionLifecycleService>();
|
||||
builder.Services.AddHostedService<ExceptionLifecycleWorker>();
|
||||
builder.Services.AddHostedService<IncidentModeExpirationWorker>();
|
||||
builder.Services.AddHostedService<PolicyEngineBootstrapWorker>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Simulation.SimulationAnalyticsService>();
|
||||
builder.Services.AddSingleton<ConsoleSimulationDiffService>();
|
||||
builder.Services.AddSingleton<StellaOps.PolicyDsl.PolicyCompiler>();
|
||||
builder.Services.AddSingleton<PolicyCompilationService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Services.PathScopeMetrics>();
|
||||
builder.Services.AddSingleton<PolicyEvaluationService>();
|
||||
builder.Services.AddPolicyEngineCore();
|
||||
builder.Services.AddSingleton<PathScopeSimulationService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Overlay.OverlayProjectionService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Overlay.IOverlayEventSink, StellaOps.Policy.Engine.Overlay.LoggingOverlayEventSink>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Overlay.OverlayChangeEventPublisher>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Overlay.PathScopeSimulationBridgeService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Scoring.IRiskScoringJobStore, StellaOps.Policy.Engine.Scoring.InMemoryRiskScoringJobStore>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Scoring.RiskScoringTriggerService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Simulation.RiskSimulationService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Signals.Entropy.EntropyPenaltyCalculator>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.RiskProfile.Export.ProfileExportService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Events.ProfileEventPublisher>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Events.IExceptionEventPublisher>(sp =>
|
||||
new StellaOps.Policy.Engine.Events.LoggingExceptionEventPublisher(
|
||||
sp.GetService<StellaOps.Policy.Engine.ExceptionCache.IExceptionEffectiveCache>(),
|
||||
sp.GetRequiredService<ILogger<StellaOps.Policy.Engine.Events.LoggingExceptionEventPublisher>>()));
|
||||
builder.Services.AddSingleton<ExceptionLifecycleService>();
|
||||
builder.Services.AddHostedService<ExceptionLifecycleWorker>();
|
||||
builder.Services.AddHostedService<IncidentModeExpirationWorker>();
|
||||
builder.Services.AddHostedService<PolicyEngineBootstrapWorker>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Simulation.SimulationAnalyticsService>();
|
||||
builder.Services.AddSingleton<ConsoleSimulationDiffService>();
|
||||
builder.Services.AddSingleton<StellaOps.PolicyDsl.PolicyCompiler>();
|
||||
builder.Services.AddSingleton<PolicyCompilationService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Services.PathScopeMetrics>();
|
||||
builder.Services.AddSingleton<PolicyEvaluationService>();
|
||||
builder.Services.AddPolicyEngineCore();
|
||||
builder.Services.AddSingleton<PathScopeSimulationService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Overlay.OverlayProjectionService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Overlay.IOverlayEventSink, StellaOps.Policy.Engine.Overlay.LoggingOverlayEventSink>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Overlay.OverlayChangeEventPublisher>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Overlay.PathScopeSimulationBridgeService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.TrustWeighting.TrustWeightingService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.AdvisoryAI.AdvisoryAiKnobsService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.BatchContext.BatchContextService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Services.EvidenceSummaryService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Services.PolicyBundleService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Services.PolicyRuntimeEvaluator>();
|
||||
builder.Services.AddSingleton<IPolicyPackRepository, InMemoryPolicyPackRepository>();
|
||||
builder.Services.AddSingleton<IOrchestratorJobStore, InMemoryOrchestratorJobStore>();
|
||||
builder.Services.AddSingleton<OrchestratorJobService>();
|
||||
builder.Services.AddSingleton<IWorkerResultStore, InMemoryWorkerResultStore>();
|
||||
builder.Services.AddSingleton<PolicyWorkerService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Ledger.ILedgerExportStore, StellaOps.Policy.Engine.Ledger.InMemoryLedgerExportStore>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Ledger.LedgerExportService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Snapshots.ISnapshotStore, StellaOps.Policy.Engine.Snapshots.InMemorySnapshotStore>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Snapshots.SnapshotService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Violations.IViolationEventStore, StellaOps.Policy.Engine.Violations.InMemoryViolationEventStore>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Violations.ViolationEventService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Violations.SeverityFusionService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Violations.ConflictHandlingService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Services.PolicyDecisionService>();
|
||||
builder.Services.AddSingleton<IExceptionRepository, InMemoryExceptionRepository>();
|
||||
builder.Services.AddSingleton<IReachabilityFactsStore, InMemoryReachabilityFactsStore>();
|
||||
builder.Services.AddSingleton<IReachabilityFactsOverlayCache, InMemoryReachabilityFactsOverlayCache>();
|
||||
builder.Services.AddSingleton<ReachabilityFactsJoiningService>();
|
||||
builder.Services.AddSingleton<IRuntimeEvaluationExecutor, RuntimeEvaluationExecutor>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Services.PolicyRuntimeEvaluator>();
|
||||
builder.Services.AddSingleton<IPolicyPackRepository, InMemoryPolicyPackRepository>();
|
||||
builder.Services.AddSingleton<IOrchestratorJobStore, InMemoryOrchestratorJobStore>();
|
||||
builder.Services.AddSingleton<OrchestratorJobService>();
|
||||
builder.Services.AddSingleton<IWorkerResultStore, InMemoryWorkerResultStore>();
|
||||
builder.Services.AddSingleton<PolicyWorkerService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Ledger.ILedgerExportStore, StellaOps.Policy.Engine.Ledger.InMemoryLedgerExportStore>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Ledger.LedgerExportService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Snapshots.ISnapshotStore, StellaOps.Policy.Engine.Snapshots.InMemorySnapshotStore>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Snapshots.SnapshotService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Violations.IViolationEventStore, StellaOps.Policy.Engine.Violations.InMemoryViolationEventStore>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Violations.ViolationEventService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Violations.SeverityFusionService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Violations.ConflictHandlingService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Services.PolicyDecisionService>();
|
||||
builder.Services.AddSingleton<IExceptionRepository, InMemoryExceptionRepository>();
|
||||
builder.Services.AddSingleton<IReachabilityFactsStore, InMemoryReachabilityFactsStore>();
|
||||
builder.Services.AddSingleton<IReachabilityFactsOverlayCache, InMemoryReachabilityFactsOverlayCache>();
|
||||
builder.Services.AddSingleton<ReachabilityFactsJoiningService>();
|
||||
builder.Services.AddSingleton<IRuntimeEvaluationExecutor, RuntimeEvaluationExecutor>();
|
||||
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
builder.Services.AddRouting(options => options.LowercaseUrls = true);
|
||||
builder.Services.AddProblemDetails();
|
||||
builder.Services.AddHealthChecks();
|
||||
|
||||
// Rate limiting configuration for simulation endpoints
|
||||
var rateLimitOptions = builder.Configuration
|
||||
.GetSection(PolicyEngineRateLimitOptions.SectionName)
|
||||
.Get<PolicyEngineRateLimitOptions>() ?? new PolicyEngineRateLimitOptions();
|
||||
|
||||
if (rateLimitOptions.Enabled)
|
||||
{
|
||||
builder.Services.AddRateLimiter(options =>
|
||||
{
|
||||
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
|
||||
|
||||
options.AddTokenBucketLimiter(PolicyEngineRateLimitOptions.PolicyName, limiterOptions =>
|
||||
{
|
||||
limiterOptions.TokenLimit = rateLimitOptions.SimulationPermitLimit;
|
||||
limiterOptions.ReplenishmentPeriod = TimeSpan.FromSeconds(rateLimitOptions.WindowSeconds);
|
||||
limiterOptions.TokensPerPeriod = rateLimitOptions.SimulationPermitLimit;
|
||||
limiterOptions.QueueLimit = rateLimitOptions.QueueLimit;
|
||||
limiterOptions.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
|
||||
});
|
||||
|
||||
options.OnRejected = async (context, cancellationToken) =>
|
||||
{
|
||||
var tenant = context.HttpContext.User.FindFirst("tenant_id")?.Value;
|
||||
var endpoint = context.HttpContext.Request.Path.Value;
|
||||
PolicyEngineTelemetry.RecordRateLimitExceeded(tenant, endpoint);
|
||||
|
||||
context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
|
||||
context.HttpContext.Response.Headers.RetryAfter = rateLimitOptions.WindowSeconds.ToString();
|
||||
|
||||
await context.HttpContext.Response.WriteAsJsonAsync(new
|
||||
{
|
||||
error = "ERR_POL_007",
|
||||
message = "Rate limit exceeded. Please retry after the reset window.",
|
||||
retryAfterSeconds = rateLimitOptions.WindowSeconds
|
||||
}, cancellationToken);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
builder.Services.AddAuthentication();
|
||||
builder.Services.AddAuthorization();
|
||||
builder.Services.AddStellaOpsScopeHandler();
|
||||
@@ -211,6 +252,11 @@ var app = builder.Build();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
if (rateLimitOptions.Enabled)
|
||||
{
|
||||
app.UseRateLimiter();
|
||||
}
|
||||
|
||||
app.MapHealthChecks("/healthz");
|
||||
app.MapGet("/readyz", (PolicyEngineStartupDiagnostics diagnostics) =>
|
||||
diagnostics.IsReady
|
||||
@@ -220,16 +266,16 @@ app.MapGet("/readyz", (PolicyEngineStartupDiagnostics diagnostics) =>
|
||||
|
||||
app.MapGet("/", () => Results.Redirect("/healthz"));
|
||||
|
||||
app.MapPolicyCompilation();
|
||||
app.MapPolicyPacks();
|
||||
app.MapPathScopeSimulation();
|
||||
app.MapOverlaySimulation();
|
||||
app.MapEvidenceSummaries();
|
||||
app.MapBatchEvaluation();
|
||||
app.MapConsoleSimulationDiff();
|
||||
app.MapTrustWeighting();
|
||||
app.MapAdvisoryAiKnobs();
|
||||
app.MapBatchContext();
|
||||
app.MapPolicyCompilation();
|
||||
app.MapPolicyPacks();
|
||||
app.MapPathScopeSimulation();
|
||||
app.MapOverlaySimulation();
|
||||
app.MapEvidenceSummaries();
|
||||
app.MapBatchEvaluation();
|
||||
app.MapConsoleSimulationDiff();
|
||||
app.MapTrustWeighting();
|
||||
app.MapAdvisoryAiKnobs();
|
||||
app.MapBatchContext();
|
||||
app.MapOrchestratorJobs();
|
||||
app.MapPolicyWorker();
|
||||
app.MapLedgerExport();
|
||||
|
||||
@@ -55,12 +55,12 @@ public static class PolicyEngineTelemetry
|
||||
unit: "overrides",
|
||||
description: "Total number of VEX overrides applied during policy evaluation.");
|
||||
|
||||
// Counter: policy_compilation_total{outcome}
|
||||
private static readonly Counter<long> PolicyCompilationCounter =
|
||||
Meter.CreateCounter<long>(
|
||||
"policy_compilation_total",
|
||||
unit: "compilations",
|
||||
description: "Total number of policy compilations attempted.");
|
||||
// Counter: policy_compilation_total{outcome}
|
||||
private static readonly Counter<long> PolicyCompilationCounter =
|
||||
Meter.CreateCounter<long>(
|
||||
"policy_compilation_total",
|
||||
unit: "compilations",
|
||||
description: "Total number of policy compilations attempted.");
|
||||
|
||||
// Histogram: policy_compilation_seconds
|
||||
private static readonly Histogram<double> PolicyCompilationSecondsHistogram =
|
||||
@@ -70,73 +70,95 @@ public static class PolicyEngineTelemetry
|
||||
description: "Duration of policy compilation.");
|
||||
|
||||
// Counter: policy_simulation_total{tenant,outcome}
|
||||
private static readonly Counter<long> PolicySimulationCounter =
|
||||
Meter.CreateCounter<long>(
|
||||
"policy_simulation_total",
|
||||
unit: "simulations",
|
||||
description: "Total number of policy simulations executed.");
|
||||
|
||||
#region Entropy Metrics
|
||||
|
||||
// Counter: policy_entropy_penalty_total{outcome}
|
||||
private static readonly Counter<long> EntropyPenaltyCounter =
|
||||
Meter.CreateCounter<long>(
|
||||
"policy_entropy_penalty_total",
|
||||
unit: "penalties",
|
||||
description: "Total entropy penalties computed from scanner evidence.");
|
||||
|
||||
// Histogram: policy_entropy_penalty_value{outcome}
|
||||
private static readonly Histogram<double> EntropyPenaltyHistogram =
|
||||
Meter.CreateHistogram<double>(
|
||||
"policy_entropy_penalty_value",
|
||||
unit: "ratio",
|
||||
description: "Entropy penalty values (after cap).");
|
||||
|
||||
// Histogram: policy_entropy_image_opaque_ratio{outcome}
|
||||
private static readonly Histogram<double> EntropyImageOpaqueRatioHistogram =
|
||||
Meter.CreateHistogram<double>(
|
||||
"policy_entropy_image_opaque_ratio",
|
||||
unit: "ratio",
|
||||
description: "Image opaque ratios observed in layer summaries.");
|
||||
|
||||
// Histogram: policy_entropy_top_file_ratio{outcome}
|
||||
private static readonly Histogram<double> EntropyTopFileRatioHistogram =
|
||||
Meter.CreateHistogram<double>(
|
||||
"policy_entropy_top_file_ratio",
|
||||
unit: "ratio",
|
||||
description: "Opaque ratio of the top offending file when present.");
|
||||
|
||||
/// <summary>
|
||||
/// Records an entropy penalty computation.
|
||||
/// </summary>
|
||||
public static void RecordEntropyPenalty(
|
||||
double penalty,
|
||||
string outcome,
|
||||
double imageOpaqueRatio,
|
||||
double? topFileOpaqueRatio = null)
|
||||
{
|
||||
var tags = new TagList
|
||||
{
|
||||
{ "outcome", NormalizeTag(outcome) },
|
||||
};
|
||||
|
||||
EntropyPenaltyCounter.Add(1, tags);
|
||||
EntropyPenaltyHistogram.Record(penalty, tags);
|
||||
EntropyImageOpaqueRatioHistogram.Record(imageOpaqueRatio, tags);
|
||||
|
||||
if (topFileOpaqueRatio.HasValue)
|
||||
{
|
||||
EntropyTopFileRatioHistogram.Record(topFileOpaqueRatio.Value, tags);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Golden Signals - Latency
|
||||
|
||||
// Histogram: policy_api_latency_seconds{endpoint,method,status}
|
||||
private static readonly Histogram<double> ApiLatencyHistogram =
|
||||
Meter.CreateHistogram<double>(
|
||||
private static readonly Counter<long> PolicySimulationCounter =
|
||||
Meter.CreateCounter<long>(
|
||||
"policy_simulation_total",
|
||||
unit: "simulations",
|
||||
description: "Total number of policy simulations executed.");
|
||||
|
||||
// Counter: policy_rate_limit_exceeded_total{tenant,endpoint}
|
||||
private static readonly Counter<long> RateLimitExceededCounter =
|
||||
Meter.CreateCounter<long>(
|
||||
"policy_rate_limit_exceeded_total",
|
||||
unit: "requests",
|
||||
description: "Total requests rejected due to rate limiting.");
|
||||
|
||||
/// <summary>
|
||||
/// Records a rate limit exceeded event.
|
||||
/// </summary>
|
||||
/// <param name="tenant">The tenant ID (or "anonymous" if not available).</param>
|
||||
/// <param name="endpoint">The endpoint that was rate limited.</param>
|
||||
public static void RecordRateLimitExceeded(string? tenant = null, string? endpoint = null)
|
||||
{
|
||||
var tags = new TagList
|
||||
{
|
||||
{ "tenant", NormalizeTag(tenant ?? "anonymous") },
|
||||
{ "endpoint", NormalizeTag(endpoint ?? "simulation") },
|
||||
};
|
||||
RateLimitExceededCounter.Add(1, tags);
|
||||
}
|
||||
|
||||
#region Entropy Metrics
|
||||
|
||||
// Counter: policy_entropy_penalty_total{outcome}
|
||||
private static readonly Counter<long> EntropyPenaltyCounter =
|
||||
Meter.CreateCounter<long>(
|
||||
"policy_entropy_penalty_total",
|
||||
unit: "penalties",
|
||||
description: "Total entropy penalties computed from scanner evidence.");
|
||||
|
||||
// Histogram: policy_entropy_penalty_value{outcome}
|
||||
private static readonly Histogram<double> EntropyPenaltyHistogram =
|
||||
Meter.CreateHistogram<double>(
|
||||
"policy_entropy_penalty_value",
|
||||
unit: "ratio",
|
||||
description: "Entropy penalty values (after cap).");
|
||||
|
||||
// Histogram: policy_entropy_image_opaque_ratio{outcome}
|
||||
private static readonly Histogram<double> EntropyImageOpaqueRatioHistogram =
|
||||
Meter.CreateHistogram<double>(
|
||||
"policy_entropy_image_opaque_ratio",
|
||||
unit: "ratio",
|
||||
description: "Image opaque ratios observed in layer summaries.");
|
||||
|
||||
// Histogram: policy_entropy_top_file_ratio{outcome}
|
||||
private static readonly Histogram<double> EntropyTopFileRatioHistogram =
|
||||
Meter.CreateHistogram<double>(
|
||||
"policy_entropy_top_file_ratio",
|
||||
unit: "ratio",
|
||||
description: "Opaque ratio of the top offending file when present.");
|
||||
|
||||
/// <summary>
|
||||
/// Records an entropy penalty computation.
|
||||
/// </summary>
|
||||
public static void RecordEntropyPenalty(
|
||||
double penalty,
|
||||
string outcome,
|
||||
double imageOpaqueRatio,
|
||||
double? topFileOpaqueRatio = null)
|
||||
{
|
||||
var tags = new TagList
|
||||
{
|
||||
{ "outcome", NormalizeTag(outcome) },
|
||||
};
|
||||
|
||||
EntropyPenaltyCounter.Add(1, tags);
|
||||
EntropyPenaltyHistogram.Record(penalty, tags);
|
||||
EntropyImageOpaqueRatioHistogram.Record(imageOpaqueRatio, tags);
|
||||
|
||||
if (topFileOpaqueRatio.HasValue)
|
||||
{
|
||||
EntropyTopFileRatioHistogram.Record(topFileOpaqueRatio.Value, tags);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Golden Signals - Latency
|
||||
|
||||
// Histogram: policy_api_latency_seconds{endpoint,method,status}
|
||||
private static readonly Histogram<double> ApiLatencyHistogram =
|
||||
Meter.CreateHistogram<double>(
|
||||
"policy_api_latency_seconds",
|
||||
unit: "s",
|
||||
description: "API request latency by endpoint.");
|
||||
@@ -419,33 +441,33 @@ public static class PolicyEngineTelemetry
|
||||
/// </summary>
|
||||
public static Counter<long> ExceptionOperations => ExceptionOperationsCounter;
|
||||
|
||||
// Counter: policy_exception_cache_operations_total{tenant,operation}
|
||||
private static readonly Counter<long> ExceptionCacheOperationsCounter =
|
||||
Meter.CreateCounter<long>(
|
||||
"policy_exception_cache_operations_total",
|
||||
unit: "operations",
|
||||
description: "Total exception cache operations (hit, miss, set, warm, invalidate).");
|
||||
|
||||
// Counter: policy_exception_applications_total{tenant,effect}
|
||||
private static readonly Counter<long> ExceptionApplicationsCounter =
|
||||
Meter.CreateCounter<long>(
|
||||
"policy_exception_applications_total",
|
||||
unit: "applications",
|
||||
description: "Total applied exceptions during evaluation by effect type.");
|
||||
|
||||
// Histogram: policy_exception_application_latency_seconds{tenant,effect}
|
||||
private static readonly Histogram<double> ExceptionApplicationLatencyHistogram =
|
||||
Meter.CreateHistogram<double>(
|
||||
"policy_exception_application_latency_seconds",
|
||||
unit: "s",
|
||||
description: "Latency impact of exception application during evaluation.");
|
||||
|
||||
// Counter: policy_exception_lifecycle_total{tenant,event}
|
||||
private static readonly Counter<long> ExceptionLifecycleCounter =
|
||||
Meter.CreateCounter<long>(
|
||||
"policy_exception_lifecycle_total",
|
||||
unit: "events",
|
||||
description: "Lifecycle events for exceptions (activated, expired, revoked).");
|
||||
// Counter: policy_exception_cache_operations_total{tenant,operation}
|
||||
private static readonly Counter<long> ExceptionCacheOperationsCounter =
|
||||
Meter.CreateCounter<long>(
|
||||
"policy_exception_cache_operations_total",
|
||||
unit: "operations",
|
||||
description: "Total exception cache operations (hit, miss, set, warm, invalidate).");
|
||||
|
||||
// Counter: policy_exception_applications_total{tenant,effect}
|
||||
private static readonly Counter<long> ExceptionApplicationsCounter =
|
||||
Meter.CreateCounter<long>(
|
||||
"policy_exception_applications_total",
|
||||
unit: "applications",
|
||||
description: "Total applied exceptions during evaluation by effect type.");
|
||||
|
||||
// Histogram: policy_exception_application_latency_seconds{tenant,effect}
|
||||
private static readonly Histogram<double> ExceptionApplicationLatencyHistogram =
|
||||
Meter.CreateHistogram<double>(
|
||||
"policy_exception_application_latency_seconds",
|
||||
unit: "s",
|
||||
description: "Latency impact of exception application during evaluation.");
|
||||
|
||||
// Counter: policy_exception_lifecycle_total{tenant,event}
|
||||
private static readonly Counter<long> ExceptionLifecycleCounter =
|
||||
Meter.CreateCounter<long>(
|
||||
"policy_exception_lifecycle_total",
|
||||
unit: "events",
|
||||
description: "Lifecycle events for exceptions (activated, expired, revoked).");
|
||||
|
||||
/// <summary>
|
||||
/// Counter for exception cache operations.
|
||||
@@ -688,58 +710,58 @@ public static class PolicyEngineTelemetry
|
||||
/// </summary>
|
||||
/// <param name="tenant">Tenant identifier.</param>
|
||||
/// <param name="operation">Operation type (hit, miss, set, warm, invalidate_*, event_*).</param>
|
||||
public static void RecordExceptionCacheOperation(string tenant, string operation)
|
||||
{
|
||||
var tags = new TagList
|
||||
{
|
||||
{ "tenant", NormalizeTenant(tenant) },
|
||||
{ "operation", NormalizeTag(operation) },
|
||||
};
|
||||
|
||||
ExceptionCacheOperationsCounter.Add(1, tags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Records that an exception was applied during evaluation.
|
||||
/// </summary>
|
||||
public static void RecordExceptionApplication(string tenant, string effectType)
|
||||
{
|
||||
var tags = new TagList
|
||||
{
|
||||
{ "tenant", NormalizeTenant(tenant) },
|
||||
{ "effect", NormalizeTag(effectType) },
|
||||
};
|
||||
|
||||
ExceptionApplicationsCounter.Add(1, tags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Records latency attributed to exception application during evaluation.
|
||||
/// </summary>
|
||||
public static void RecordExceptionApplicationLatency(double seconds, string tenant, string effectType)
|
||||
{
|
||||
var tags = new TagList
|
||||
{
|
||||
{ "tenant", NormalizeTenant(tenant) },
|
||||
{ "effect", NormalizeTag(effectType) },
|
||||
};
|
||||
|
||||
ExceptionApplicationLatencyHistogram.Record(seconds, tags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Records an exception lifecycle event (activated, expired, revoked).
|
||||
/// </summary>
|
||||
public static void RecordExceptionLifecycle(string tenant, string eventType)
|
||||
{
|
||||
var tags = new TagList
|
||||
{
|
||||
{ "tenant", NormalizeTenant(tenant) },
|
||||
{ "event", NormalizeTag(eventType) },
|
||||
};
|
||||
|
||||
ExceptionLifecycleCounter.Add(1, tags);
|
||||
}
|
||||
public static void RecordExceptionCacheOperation(string tenant, string operation)
|
||||
{
|
||||
var tags = new TagList
|
||||
{
|
||||
{ "tenant", NormalizeTenant(tenant) },
|
||||
{ "operation", NormalizeTag(operation) },
|
||||
};
|
||||
|
||||
ExceptionCacheOperationsCounter.Add(1, tags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Records that an exception was applied during evaluation.
|
||||
/// </summary>
|
||||
public static void RecordExceptionApplication(string tenant, string effectType)
|
||||
{
|
||||
var tags = new TagList
|
||||
{
|
||||
{ "tenant", NormalizeTenant(tenant) },
|
||||
{ "effect", NormalizeTag(effectType) },
|
||||
};
|
||||
|
||||
ExceptionApplicationsCounter.Add(1, tags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Records latency attributed to exception application during evaluation.
|
||||
/// </summary>
|
||||
public static void RecordExceptionApplicationLatency(double seconds, string tenant, string effectType)
|
||||
{
|
||||
var tags = new TagList
|
||||
{
|
||||
{ "tenant", NormalizeTenant(tenant) },
|
||||
{ "effect", NormalizeTag(effectType) },
|
||||
};
|
||||
|
||||
ExceptionApplicationLatencyHistogram.Record(seconds, tags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Records an exception lifecycle event (activated, expired, revoked).
|
||||
/// </summary>
|
||||
public static void RecordExceptionLifecycle(string tenant, string eventType)
|
||||
{
|
||||
var tags = new TagList
|
||||
{
|
||||
{ "tenant", NormalizeTenant(tenant) },
|
||||
{ "event", NormalizeTag(eventType) },
|
||||
};
|
||||
|
||||
ExceptionLifecycleCounter.Add(1, tags);
|
||||
}
|
||||
|
||||
#region Golden Signals - Recording Methods
|
||||
|
||||
|
||||
Reference in New Issue
Block a user