audit work, fixed StellaOps.sln warnings/errors, fixed tests, sprints work, new advisories
This commit is contained in:
@@ -37,4 +37,11 @@ public sealed record EpssEvidence
|
||||
/// </summary>
|
||||
[JsonPropertyName("model_version")]
|
||||
public string? ModelVersion { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Convenience property returning the EPSS score.
|
||||
/// Alias for <see cref="Epss"/> for API compatibility.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public double Score => Epss;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,18 @@ public sealed record ReachabilityEvidence
|
||||
/// </summary>
|
||||
[JsonPropertyName("witness_digest")]
|
||||
public string? WitnessDigest { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Analysis confidence [0.0, 1.0].
|
||||
/// </summary>
|
||||
[JsonPropertyName("confidence")]
|
||||
public double Confidence { get; init; } = 1.0;
|
||||
|
||||
/// <summary>
|
||||
/// Convenience property indicating if code is reachable.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public bool IsReachable => Status == ReachabilityStatus.Reachable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -42,4 +42,11 @@ public sealed record RuntimeEvidence
|
||||
/// </summary>
|
||||
[JsonPropertyName("confidence")]
|
||||
public required double Confidence { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Convenience property indicating if vulnerable code was observed loaded.
|
||||
/// Alias for <see cref="Detected"/> for API compatibility.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public bool ObservedLoaded => Detected;
|
||||
}
|
||||
|
||||
@@ -37,4 +37,17 @@ public sealed record VexClaimSummary
|
||||
/// </summary>
|
||||
[JsonPropertyName("justification")]
|
||||
public string? Justification { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Convenience property indicating if the VEX status is "not_affected".
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public bool IsNotAffected => string.Equals(Status, "not_affected", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Issuer trust level [0.0, 1.0].
|
||||
/// Alias for <see cref="Confidence"/> for API compatibility.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public double IssuerTrust => Confidence;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace StellaOps.Policy.Determinization.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Context for determinization evaluation.
|
||||
/// Contains environment, criticality, and policy settings.
|
||||
/// Contains environment, criticality, policy settings, and computed evidence data.
|
||||
/// </summary>
|
||||
public sealed record DeterminizationContext
|
||||
{
|
||||
@@ -18,56 +18,116 @@ public sealed record DeterminizationContext
|
||||
/// Asset criticality level.
|
||||
/// </summary>
|
||||
[JsonPropertyName("criticality")]
|
||||
public required AssetCriticality Criticality { get; init; }
|
||||
public AssetCriticality Criticality { get; init; } = AssetCriticality.Medium;
|
||||
|
||||
/// <summary>
|
||||
/// Entropy threshold for this context.
|
||||
/// Observations above this trigger guardrails.
|
||||
/// </summary>
|
||||
[JsonPropertyName("entropy_threshold")]
|
||||
public required double EntropyThreshold { get; init; }
|
||||
public double EntropyThreshold { get; init; } = 0.5;
|
||||
|
||||
/// <summary>
|
||||
/// Decay threshold for this context.
|
||||
/// Observations below this are considered stale.
|
||||
/// </summary>
|
||||
[JsonPropertyName("decay_threshold")]
|
||||
public required double DecayThreshold { get; init; }
|
||||
public double DecayThreshold { get; init; } = 0.5;
|
||||
|
||||
/// <summary>
|
||||
/// Creates context with default production settings.
|
||||
/// Signal snapshot containing evidence from various sources.
|
||||
/// </summary>
|
||||
public static DeterminizationContext Production() => new()
|
||||
{
|
||||
Environment = DeploymentEnvironment.Production,
|
||||
Criticality = AssetCriticality.High,
|
||||
EntropyThreshold = 0.4,
|
||||
DecayThreshold = 0.50
|
||||
};
|
||||
[JsonPropertyName("signal_snapshot")]
|
||||
public required SignalSnapshot SignalSnapshot { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates context with relaxed development settings.
|
||||
/// Calculated uncertainty score for this context.
|
||||
/// </summary>
|
||||
public static DeterminizationContext Development() => new()
|
||||
{
|
||||
Environment = DeploymentEnvironment.Development,
|
||||
Criticality = AssetCriticality.Low,
|
||||
EntropyThreshold = 0.6,
|
||||
DecayThreshold = 0.35
|
||||
};
|
||||
[JsonPropertyName("uncertainty_score")]
|
||||
public required UncertaintyScore UncertaintyScore { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates context with custom thresholds.
|
||||
/// Observation decay information.
|
||||
/// </summary>
|
||||
[JsonPropertyName("decay")]
|
||||
public required ObservationDecay Decay { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Aggregated trust score (0.0-1.0).
|
||||
/// </summary>
|
||||
[JsonPropertyName("trust_score")]
|
||||
public required double TrustScore { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates context with default production settings and placeholder signal data.
|
||||
/// </summary>
|
||||
public static DeterminizationContext Production(
|
||||
SignalSnapshot? snapshot = null,
|
||||
UncertaintyScore? uncertaintyScore = null,
|
||||
ObservationDecay? decay = null,
|
||||
double trustScore = 0.5)
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
return new()
|
||||
{
|
||||
Environment = DeploymentEnvironment.Production,
|
||||
Criticality = AssetCriticality.High,
|
||||
EntropyThreshold = 0.4,
|
||||
DecayThreshold = 0.50,
|
||||
SignalSnapshot = snapshot ?? SignalSnapshot.Empty("CVE-0000-0000", "pkg:unknown/unknown@0.0.0", now),
|
||||
UncertaintyScore = uncertaintyScore ?? UncertaintyScore.Zero(1.0, now),
|
||||
Decay = decay ?? ObservationDecay.Fresh(now),
|
||||
TrustScore = trustScore
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates context with relaxed development settings and placeholder signal data.
|
||||
/// </summary>
|
||||
public static DeterminizationContext Development(
|
||||
SignalSnapshot? snapshot = null,
|
||||
UncertaintyScore? uncertaintyScore = null,
|
||||
ObservationDecay? decay = null,
|
||||
double trustScore = 0.5)
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
return new()
|
||||
{
|
||||
Environment = DeploymentEnvironment.Development,
|
||||
Criticality = AssetCriticality.Low,
|
||||
EntropyThreshold = 0.6,
|
||||
DecayThreshold = 0.35,
|
||||
SignalSnapshot = snapshot ?? SignalSnapshot.Empty("CVE-0000-0000", "pkg:unknown/unknown@0.0.0", now),
|
||||
UncertaintyScore = uncertaintyScore ?? UncertaintyScore.Zero(1.0, now),
|
||||
Decay = decay ?? ObservationDecay.Fresh(now),
|
||||
TrustScore = trustScore
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates context with custom thresholds and placeholder signal data.
|
||||
/// </summary>
|
||||
public static DeterminizationContext Create(
|
||||
DeploymentEnvironment environment,
|
||||
AssetCriticality criticality,
|
||||
double entropyThreshold,
|
||||
double decayThreshold) => new()
|
||||
double decayThreshold,
|
||||
SignalSnapshot? snapshot = null,
|
||||
UncertaintyScore? uncertaintyScore = null,
|
||||
ObservationDecay? decay = null,
|
||||
double trustScore = 0.5)
|
||||
{
|
||||
Environment = environment,
|
||||
Criticality = criticality,
|
||||
EntropyThreshold = entropyThreshold,
|
||||
DecayThreshold = decayThreshold
|
||||
};
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
return new()
|
||||
{
|
||||
Environment = environment,
|
||||
Criticality = criticality,
|
||||
EntropyThreshold = entropyThreshold,
|
||||
DecayThreshold = decayThreshold,
|
||||
SignalSnapshot = snapshot ?? SignalSnapshot.Empty("CVE-0000-0000", "pkg:unknown/unknown@0.0.0", now),
|
||||
UncertaintyScore = uncertaintyScore ?? UncertaintyScore.Zero(1.0, now),
|
||||
Decay = decay ?? ObservationDecay.Fresh(now),
|
||||
TrustScore = trustScore
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,10 +38,22 @@ public sealed record GuardRails
|
||||
[JsonPropertyName("notes")]
|
||||
public string? Notes { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Default guardrails instance with safe settings.
|
||||
/// </summary>
|
||||
public static GuardRails Default { get; } = new()
|
||||
{
|
||||
EnableMonitoring = true,
|
||||
RestrictToNonProd = false,
|
||||
RequireApproval = false,
|
||||
ReevalAfter = TimeSpan.FromDays(7),
|
||||
Notes = null
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Creates GuardRails with default safe settings.
|
||||
/// </summary>
|
||||
public static GuardRails Default() => new()
|
||||
public static GuardRails CreateDefault() => new()
|
||||
{
|
||||
EnableMonitoring = true,
|
||||
RestrictToNonProd = false,
|
||||
|
||||
@@ -3,7 +3,7 @@ using System.Text.Json.Serialization;
|
||||
namespace StellaOps.Policy.Determinization.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Per-observation decay configuration.
|
||||
/// Per-observation decay configuration and computed state.
|
||||
/// Tracks evidence staleness with configurable half-life.
|
||||
/// Formula: decayed = max(floor, exp(-ln(2) * age_days / half_life_days))
|
||||
/// </summary>
|
||||
@@ -13,27 +13,27 @@ public sealed record ObservationDecay
|
||||
/// When the observation was first recorded (UTC).
|
||||
/// </summary>
|
||||
[JsonPropertyName("observed_at")]
|
||||
public required DateTimeOffset ObservedAt { get; init; }
|
||||
public DateTimeOffset ObservedAt { get; init; } = DateTimeOffset.UtcNow;
|
||||
|
||||
/// <summary>
|
||||
/// When the observation was last refreshed (UTC).
|
||||
/// </summary>
|
||||
[JsonPropertyName("refreshed_at")]
|
||||
public required DateTimeOffset RefreshedAt { get; init; }
|
||||
public DateTimeOffset RefreshedAt { get; init; } = DateTimeOffset.UtcNow;
|
||||
|
||||
/// <summary>
|
||||
/// Half-life in days.
|
||||
/// Default: 14 days.
|
||||
/// </summary>
|
||||
[JsonPropertyName("half_life_days")]
|
||||
public required double HalfLifeDays { get; init; }
|
||||
public double HalfLifeDays { get; init; } = 14.0;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum confidence floor.
|
||||
/// Default: 0.35 (consistent with FreshnessCalculator).
|
||||
/// </summary>
|
||||
[JsonPropertyName("floor")]
|
||||
public required double Floor { get; init; }
|
||||
public double Floor { get; init; } = 0.35;
|
||||
|
||||
/// <summary>
|
||||
/// Staleness threshold (0.0-1.0).
|
||||
@@ -41,7 +41,31 @@ public sealed record ObservationDecay
|
||||
/// Default: 0.50
|
||||
/// </summary>
|
||||
[JsonPropertyName("staleness_threshold")]
|
||||
public required double StalenessThreshold { get; init; }
|
||||
public double StalenessThreshold { get; init; } = 0.50;
|
||||
|
||||
/// <summary>
|
||||
/// Last signal update time (alias for RefreshedAt for convenience).
|
||||
/// </summary>
|
||||
[JsonPropertyName("last_signal_update")]
|
||||
public DateTimeOffset LastSignalUpdate { get; init; } = DateTimeOffset.UtcNow;
|
||||
|
||||
/// <summary>
|
||||
/// Age in days since last refresh.
|
||||
/// </summary>
|
||||
[JsonPropertyName("age_days")]
|
||||
public double AgeDays { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Pre-computed decay multiplier (0.0-1.0).
|
||||
/// </summary>
|
||||
[JsonPropertyName("decayed_multiplier")]
|
||||
public double DecayedMultiplier { get; init; } = 1.0;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the observation is considered stale.
|
||||
/// </summary>
|
||||
[JsonPropertyName("is_stale")]
|
||||
public bool IsStale { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the current decay multiplier.
|
||||
@@ -59,7 +83,7 @@ public sealed record ObservationDecay
|
||||
/// <summary>
|
||||
/// Returns true if the observation is stale (decay below threshold).
|
||||
/// </summary>
|
||||
public bool IsStale(DateTimeOffset now) =>
|
||||
public bool CheckIsStale(DateTimeOffset now) =>
|
||||
CalculateDecay(now) < StalenessThreshold;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -82,6 +82,12 @@ public sealed record UncertaintyScore
|
||||
[JsonPropertyName("calculated_at")]
|
||||
public required DateTimeOffset CalculatedAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Completeness ratio (present_weight / max_weight).
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public double Completeness => MaxWeight > 0 ? PresentWeight / MaxWeight : 0.0;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an UncertaintyScore with calculated tier.
|
||||
/// </summary>
|
||||
|
||||
@@ -11,6 +11,16 @@ namespace StellaOps.Policy.Determinization;
|
||||
/// </summary>
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers determinization services with default options.
|
||||
/// </summary>
|
||||
/// <param name="services">Service collection</param>
|
||||
/// <returns>Service collection for chaining</returns>
|
||||
public static IServiceCollection AddDeterminization(this IServiceCollection services)
|
||||
{
|
||||
return services.AddDeterminization(_ => { });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers determinization services with the DI container.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
|
||||
namespace StellaOps.Policy.Exceptions.Models;
|
||||
|
||||
@@ -255,8 +256,8 @@ public sealed record ExceptionEvent
|
||||
NewVersion = newVersion,
|
||||
Description = reason ?? $"Exception extended from {previousExpiry:O} to {newExpiry:O}",
|
||||
Details = ImmutableDictionary<string, string>.Empty
|
||||
.Add("previous_expiry", previousExpiry.ToString("O"))
|
||||
.Add("new_expiry", newExpiry.ToString("O")),
|
||||
.Add("previous_expiry", previousExpiry.ToString("O", CultureInfo.InvariantCulture))
|
||||
.Add("new_expiry", newExpiry.ToString("O", CultureInfo.InvariantCulture)),
|
||||
ClientInfo = clientInfo
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.Text.Json.Nodes;
|
||||
using StellaOps.Policy.Unknowns.Models;
|
||||
|
||||
@@ -81,7 +82,7 @@ public static class BudgetExceededEventFactory
|
||||
["action"] = payload.Action,
|
||||
["totalUnknowns"] = payload.TotalUnknowns,
|
||||
["violationCount"] = payload.ViolationCount,
|
||||
["timestamp"] = payload.Timestamp.ToString("O")
|
||||
["timestamp"] = payload.Timestamp.ToString("O", CultureInfo.InvariantCulture)
|
||||
};
|
||||
|
||||
if (payload.TotalLimit.HasValue)
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
@@ -147,7 +148,7 @@ public sealed class BudgetThresholdNotifier
|
||||
["percentageUsed"] = budget.PercentageUsed,
|
||||
["status"] = budget.Status.ToString().ToLowerInvariant(),
|
||||
["severity"] = severity,
|
||||
["timestamp"] = timeProvider.GetUtcNow().ToString("O")
|
||||
["timestamp"] = timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.Json;
|
||||
@@ -133,7 +134,7 @@ public static class PolicyDigest
|
||||
|
||||
if (rule.Expires is DateTimeOffset expires)
|
||||
{
|
||||
writer.WriteString("expires", expires.ToUniversalTime().ToString("O"));
|
||||
writer.WriteString("expires", expires.ToUniversalTime().ToString("O", CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(rule.Justification))
|
||||
@@ -159,7 +160,7 @@ public static class PolicyDigest
|
||||
{
|
||||
if (ignore.Until is DateTimeOffset until)
|
||||
{
|
||||
writer.WriteString("until", until.ToUniversalTime().ToString("O"));
|
||||
writer.WriteString("until", until.ToUniversalTime().ToString("O", CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(ignore.Justification))
|
||||
|
||||
@@ -4,16 +4,8 @@ using StellaOps.Policy.Determinization.Models;
|
||||
|
||||
namespace StellaOps.Policy;
|
||||
|
||||
/// <summary>
|
||||
/// Runtime monitoring requirements for GuardedPass verdicts.
|
||||
/// </summary>
|
||||
/// <param name="MonitoringIntervalDays">Days between re-evaluation checks.</param>
|
||||
/// <param name="RequireProof">Whether runtime proof is required before production deployment.</param>
|
||||
/// <param name="AlertOnChange">Whether to send alerts if verdict changes on re-evaluation.</param>
|
||||
public sealed record GuardRails(
|
||||
int MonitoringIntervalDays,
|
||||
bool RequireProof,
|
||||
bool AlertOnChange);
|
||||
// NOTE: GuardRails type has been consolidated into StellaOps.Policy.Determinization.Models.GuardRails.
|
||||
// Use that type for runtime monitoring requirements in GuardedPass verdicts.
|
||||
|
||||
/// <summary>
|
||||
/// Status outcomes for policy verdicts.
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
// Description: Deterministic hashing for proof nodes and root hash computation
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
@@ -110,7 +111,7 @@ public static class ProofHashing
|
||||
["ruleId"] = node.RuleId,
|
||||
["seed"] = Convert.ToBase64String(node.Seed),
|
||||
["total"] = node.Total,
|
||||
["tsUtc"] = node.TsUtc.ToUniversalTime().ToString("O")
|
||||
["tsUtc"] = node.TsUtc.ToUniversalTime().ToString("O", CultureInfo.InvariantCulture)
|
||||
};
|
||||
|
||||
return SerializeCanonical(obj);
|
||||
|
||||
Reference in New Issue
Block a user