audit work, fixed StellaOps.sln warnings/errors, fixed tests, sprints work, new advisories

This commit is contained in:
master
2026-01-07 18:49:59 +02:00
parent 04ec098046
commit 608a7f85c0
866 changed files with 56323 additions and 6231 deletions

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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
};
}
}

View File

@@ -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,

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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
};
}

View File

@@ -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)

View File

@@ -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)
};
}
}

View File

@@ -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))

View File

@@ -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.

View File

@@ -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);