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

@@ -1,4 +1,5 @@
using System.Collections.Immutable;
using System.Globalization;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@@ -264,7 +265,7 @@ internal sealed class ExceptionAdapter : IExceptionAdapter
builder["exception.owner"] = exception.OwnerId;
builder["exception.requester"] = exception.RequesterId;
builder["exception.rationale"] = exception.Rationale;
builder["exception.expiresAt"] = exception.ExpiresAt.ToString("O");
builder["exception.expiresAt"] = exception.ExpiresAt.ToString("O", CultureInfo.InvariantCulture);
if (exception.ApproverIds.Length > 0)
{

View File

@@ -1,3 +1,4 @@
using System.Globalization;
using Microsoft.Extensions.Logging;
namespace StellaOps.Policy.Engine.AirGap;
@@ -393,7 +394,7 @@ internal sealed class WebhookNotificationChannel : IAirGapNotificationChannel
severity = notification.Severity.ToString(),
title = notification.Title,
message = notification.Message,
occurred_at = notification.OccurredAt.ToString("O"),
occurred_at = notification.OccurredAt.ToString("O", CultureInfo.InvariantCulture),
metadata = notification.Metadata
};

View File

@@ -1,3 +1,4 @@
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
@@ -68,7 +69,7 @@ internal sealed class PolicyPackBundleImportService
TenantId: tenantId,
Status: BundleImportStatus.Validating,
ExportCount: 0,
ImportedAt: now.ToString("O"),
ImportedAt: now.ToString("O", CultureInfo.InvariantCulture),
Error: null,
Bundle: null);
@@ -163,7 +164,7 @@ internal sealed class PolicyPackBundleImportService
TenantId: tenantId,
Status: BundleImportStatus.Imported,
ExportCount: bundle.Exports.Count,
ImportedAt: now.ToString("O"),
ImportedAt: now.ToString("O", CultureInfo.InvariantCulture),
Error: null,
Bundle: bundle);

View File

@@ -1,3 +1,4 @@
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
@@ -76,7 +77,7 @@ public sealed class RiskProfileAirGapExportService
ExportId: Guid.NewGuid().ToString("N")[..16],
ProfileId: profile.Id,
ProfileVersion: profile.Version,
CreatedAt: now.ToString("O"),
CreatedAt: now.ToString("O", CultureInfo.InvariantCulture),
ArtifactSizeBytes: Encoding.UTF8.GetByteCount(profileJson),
ArtifactDigest: artifactDigest,
ContentHash: contentHash,
@@ -99,7 +100,7 @@ public sealed class RiskProfileAirGapExportService
return new RiskProfileAirGapBundle(
SchemaVersion: 1,
GeneratedAt: now.ToString("O"),
GeneratedAt: now.ToString("O", CultureInfo.InvariantCulture),
TargetRepository: request.TargetRepository,
DomainId: DomainId,
DisplayName: request.DisplayName ?? "Risk Profiles Export",
@@ -337,7 +338,7 @@ public sealed class RiskProfileAirGapExportService
Algorithm: "HMAC-SHA256",
KeyId: keyId ?? "default",
Provider: "stellaops",
SignedAt: signedAt.ToString("O"));
SignedAt: signedAt.ToString("O", CultureInfo.InvariantCulture));
}
private static string ComputeSignatureData(List<RiskProfileAirGapExport> exports, string merkleRoot)
@@ -422,7 +423,7 @@ public sealed class RiskProfileAirGapExportService
PredicateType: PredicateType,
RekorLocation: null,
EnvelopeDigest: null,
SignedAt: signedAt.ToString("O"));
SignedAt: signedAt.ToString("O", CultureInfo.InvariantCulture));
}
private static string GenerateBundleId(DateTimeOffset timestamp)

View File

@@ -6,6 +6,7 @@
// -----------------------------------------------------------------------------
using System.Collections.Immutable;
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
@@ -129,7 +130,7 @@ public sealed record ScoreProvenanceChain
rule_name = Verdict.MatchedRuleName,
verdict_digest = Verdict.VerdictDigest
},
created_at = CreatedAt.ToUniversalTime().ToString("O")
created_at = CreatedAt.ToUniversalTime().ToString("O", CultureInfo.InvariantCulture)
};
var json = JsonSerializer.Serialize(canonical, ProvenanceJsonOptions.Default);
@@ -659,7 +660,7 @@ public sealed record ProvenanceVerdictRef
status = predicate.Verdict.Status,
severity = predicate.Verdict.Severity,
score = predicate.Verdict.Score,
evaluated_at = predicate.EvaluatedAt.ToUniversalTime().ToString("O")
evaluated_at = predicate.EvaluatedAt.ToUniversalTime().ToString("O", CultureInfo.InvariantCulture)
};
var json = JsonSerializer.Serialize(canonical, ProvenanceJsonOptions.Default);

View File

@@ -1,3 +1,4 @@
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
@@ -38,7 +39,7 @@ internal sealed class BatchContextService
ComputeTraceRef(request.TenantId, i)))
.ToList();
var expires = _timeProvider.GetUtcNow().AddHours(1).ToString("O");
var expires = _timeProvider.GetUtcNow().AddHours(1).ToString("O", CultureInfo.InvariantCulture);
var contextId = ComputeContextId(request, sortedItems);
return new BatchContextResponse(

View File

@@ -1,3 +1,4 @@
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
@@ -58,7 +59,7 @@ internal sealed partial class ConsoleExportJobService
Destination: request.Destination,
Signing: request.Signing,
Enabled: true,
CreatedAt: now.ToString("O"),
CreatedAt: now.ToString("O", CultureInfo.InvariantCulture),
LastRunAt: null,
NextRunAt: CalculateNextRun(request.Schedule, now));
@@ -128,7 +129,7 @@ internal sealed partial class ConsoleExportJobService
JobId: jobId,
Status: "running",
BundleId: null,
StartedAt: now.ToString("O"),
StartedAt: now.ToString("O", CultureInfo.InvariantCulture),
CompletedAt: null,
Error: null);
@@ -177,7 +178,7 @@ internal sealed partial class ConsoleExportJobService
BundleId: bundleId,
JobId: job.JobId,
TenantId: job.TenantId,
CreatedAt: now.ToString("O"),
CreatedAt: now.ToString("O", CultureInfo.InvariantCulture),
Format: job.Format,
ArtifactDigest: artifactDigest,
ArtifactSizeBytes: contentBytes.Length,
@@ -195,14 +196,14 @@ internal sealed partial class ConsoleExportJobService
{
Status = "completed",
BundleId = bundleId,
CompletedAt = now.ToString("O")
CompletedAt = now.ToString("O", CultureInfo.InvariantCulture)
};
await _executionStore.SaveAsync(completedExecution, cancellationToken).ConfigureAwait(false);
// Update job with last run
var updatedJob = job with
{
LastRunAt = now.ToString("O"),
LastRunAt = now.ToString("O", CultureInfo.InvariantCulture),
NextRunAt = CalculateNextRun(job.Schedule, now)
};
await _jobStore.SaveAsync(updatedJob, cancellationToken).ConfigureAwait(false);
@@ -212,7 +213,7 @@ internal sealed partial class ConsoleExportJobService
var failedExecution = execution with
{
Status = "failed",
CompletedAt = _timeProvider.GetUtcNow().ToString("O"),
CompletedAt = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture),
Error = ex.Message
};
await _executionStore.SaveAsync(failedExecution, CancellationToken.None).ConfigureAwait(false);
@@ -269,7 +270,7 @@ internal sealed partial class ConsoleExportJobService
// In production, this would use a proper cron parser like Cronos
if (schedule.StartsWith("0 0 ", StringComparison.Ordinal))
{
return from.AddDays(1).ToString("O");
return from.AddDays(1).ToString("O", CultureInfo.InvariantCulture);
}
if (schedule.StartsWith("0 */", StringComparison.Ordinal))
@@ -277,11 +278,11 @@ internal sealed partial class ConsoleExportJobService
var hourMatch = Regex.Match(schedule, @"\*/(\d+)");
if (hourMatch.Success && int.TryParse(hourMatch.Groups[1].Value, out var hours))
{
return from.AddHours(hours).ToString("O");
return from.AddHours(hours).ToString("O", CultureInfo.InvariantCulture);
}
}
return from.AddDays(1).ToString("O");
return from.AddDays(1).ToString("O", CultureInfo.InvariantCulture);
}
private static string GenerateId(string prefix)

View File

@@ -21,6 +21,9 @@ public static class DeterminizationEngineExtensions
// Add determinization library services
services.AddDeterminization();
// Add metrics
services.TryAddSingleton<DeterminizationGateMetrics>();
// Add gate
services.TryAddSingleton<IDeterminizationGate, DeterminizationGate>();

View File

@@ -294,7 +294,7 @@ public static class PolicyEngineServiceCollectionExtensions
/// Adds all Policy Engine services with default configuration.
/// </summary>
/// <remarks>
/// Includes core services, event pipeline, worker, explainer, and Evidence-Weighted Score services.
/// Includes core services, event pipeline, worker, explainer, determinization gate, and Evidence-Weighted Score services.
/// EWS services are registered but only activate when <see cref="PolicyEvidenceWeightedScoreOptions.Enabled"/> is true.
/// </remarks>
public static IServiceCollection AddPolicyEngine(this IServiceCollection services)
@@ -304,6 +304,9 @@ public static class PolicyEngineServiceCollectionExtensions
services.AddPolicyEngineWorker();
services.AddPolicyEngineExplainer();
// Determinization gate and policy services (Sprint 20260106_001_003)
services.AddDeterminizationEngine();
// Evidence-Weighted Score services (Sprint 8200.0012.0003)
// Always registered; activation controlled by PolicyEvidenceWeightedScoreOptions.Enabled
services.AddEvidenceWeightedScore();
@@ -342,6 +345,9 @@ public static class PolicyEngineServiceCollectionExtensions
services.AddPolicyEngineWorker();
services.AddPolicyEngineExplainer();
// Determinization gate and policy services (Sprint 20260106_001_003)
services.AddDeterminizationEngine();
// Conditional EWS registration based on configuration
services.AddEvidenceWeightedScoreIfEnabled(configuration);

View File

@@ -1,5 +1,6 @@
using System.Collections.Immutable;
using System.Diagnostics;
using System.Globalization;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@@ -544,7 +545,7 @@ internal sealed class MessagingExceptionEffectiveCache : IExceptionEffectiveCach
var statsKey = GetStatsKey(tenantId);
var stats = new Dictionary<string, string>
{
["lastWarmAt"] = warmAt.ToString("O"),
["lastWarmAt"] = warmAt.ToString("O", CultureInfo.InvariantCulture),
["lastWarmCount"] = count.ToString(),
};

View File

@@ -1,5 +1,6 @@
using System.Collections.Immutable;
using System.Diagnostics;
using System.Globalization;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@@ -642,7 +643,7 @@ internal sealed class RedisExceptionEffectiveCache : IExceptionEffectiveCache
var stats = new Dictionary<string, string>
{
["lastWarmAt"] = warmAt.ToString("O"),
["lastWarmAt"] = warmAt.ToString("O", CultureInfo.InvariantCulture),
["lastWarmCount"] = count.ToString(),
};

View File

@@ -4,6 +4,7 @@ using Microsoft.Extensions.Logging;
using StellaOps.Policy;
using StellaOps.Policy.Determinization;
using StellaOps.Policy.Determinization.Models;
using StellaOps.Policy.Determinization.Scoring;
using StellaOps.Policy.Engine.Gates.Determinization;
using StellaOps.Policy.Engine.Policies;
using StellaOps.Policy.Gates;

View File

@@ -0,0 +1,163 @@
using System.Diagnostics;
using System.Diagnostics.Metrics;
using StellaOps.Policy.Determinization.Models;
namespace StellaOps.Policy.Engine.Gates.Determinization;
/// <summary>
/// OpenTelemetry metrics for determinization gate and observation state tracking.
/// </summary>
public sealed class DeterminizationGateMetrics : IDisposable
{
private readonly Meter _meter;
private readonly Counter<long> _evaluationsTotal;
private readonly Counter<long> _ruleMatchesTotal;
private readonly Counter<long> _stateTransitionsTotal;
private readonly Histogram<double> _entropyDistribution;
private readonly Histogram<double> _trustScoreDistribution;
private readonly Histogram<double> _evaluationDurationMs;
public const string MeterName = "StellaOps.Policy.Engine.DeterminizationGate";
public DeterminizationGateMetrics()
{
_meter = new Meter(MeterName, "1.0.0");
_evaluationsTotal = _meter.CreateCounter<long>(
"stellaops_policy_determinization_evaluations_total",
unit: "{evaluations}",
description: "Total number of determinization gate evaluations");
_ruleMatchesTotal = _meter.CreateCounter<long>(
"stellaops_policy_determinization_rule_matches_total",
unit: "{matches}",
description: "Total number of determinization rule matches by rule name");
_stateTransitionsTotal = _meter.CreateCounter<long>(
"stellaops_policy_observation_state_transitions_total",
unit: "{transitions}",
description: "Total number of observation state transitions");
_entropyDistribution = _meter.CreateHistogram<double>(
"stellaops_policy_determinization_entropy",
unit: "1",
description: "Distribution of entropy scores evaluated");
_trustScoreDistribution = _meter.CreateHistogram<double>(
"stellaops_policy_determinization_trust_score",
unit: "1",
description: "Distribution of trust scores evaluated");
_evaluationDurationMs = _meter.CreateHistogram<double>(
"stellaops_policy_determinization_evaluation_duration_ms",
unit: "ms",
description: "Duration of determinization gate evaluations");
}
/// <summary>
/// Record a gate evaluation.
/// </summary>
public void RecordEvaluation(
PolicyVerdictStatus status,
string environment,
string? matchedRule)
{
_evaluationsTotal.Add(1,
new KeyValuePair<string, object?>("status", status.ToString().ToLowerInvariant()),
new KeyValuePair<string, object?>("environment", environment),
new KeyValuePair<string, object?>("rule", matchedRule ?? "none"));
}
/// <summary>
/// Record a rule match.
/// </summary>
public void RecordRuleMatch(
string ruleName,
PolicyVerdictStatus status,
string environment)
{
_ruleMatchesTotal.Add(1,
new KeyValuePair<string, object?>("rule", ruleName),
new KeyValuePair<string, object?>("status", status.ToString().ToLowerInvariant()),
new KeyValuePair<string, object?>("environment", environment));
}
/// <summary>
/// Record an observation state transition.
/// </summary>
public void RecordStateTransition(
ObservationState fromState,
ObservationState toState,
string trigger,
string environment)
{
_stateTransitionsTotal.Add(1,
new KeyValuePair<string, object?>("from_state", fromState.ToString().ToLowerInvariant()),
new KeyValuePair<string, object?>("to_state", toState.ToString().ToLowerInvariant()),
new KeyValuePair<string, object?>("trigger", trigger),
new KeyValuePair<string, object?>("environment", environment));
}
/// <summary>
/// Record entropy value from evaluation.
/// </summary>
public void RecordEntropy(double entropy, string environment)
{
_entropyDistribution.Record(
entropy,
new KeyValuePair<string, object?>("environment", environment));
}
/// <summary>
/// Record trust score from evaluation.
/// </summary>
public void RecordTrustScore(double trustScore, string environment)
{
_trustScoreDistribution.Record(
trustScore,
new KeyValuePair<string, object?>("environment", environment));
}
/// <summary>
/// Record evaluation duration.
/// </summary>
public void RecordDuration(TimeSpan duration, string environment)
{
_evaluationDurationMs.Record(
duration.TotalMilliseconds,
new KeyValuePair<string, object?>("environment", environment));
}
/// <summary>
/// Create a timer scope for measuring evaluation duration.
/// </summary>
public IDisposable StartEvaluationTimer(string environment)
{
return new DurationScope(this, environment);
}
public void Dispose()
{
_meter.Dispose();
}
private sealed class DurationScope : IDisposable
{
private readonly DeterminizationGateMetrics _metrics;
private readonly string _environment;
private readonly Stopwatch _stopwatch;
public DurationScope(DeterminizationGateMetrics metrics, string environment)
{
_metrics = metrics;
_environment = environment;
_stopwatch = Stopwatch.StartNew();
}
public void Dispose()
{
_stopwatch.Stop();
_metrics.RecordDuration(_stopwatch.Elapsed, _environment);
}
}
}

View File

@@ -1,6 +1,7 @@
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
@@ -396,7 +397,7 @@ public sealed class IncrementalPolicyOrchestrator
{
var builder = new StringBuilder();
builder.Append(tenantId).Append('|');
builder.Append(createdAt.ToString("O")).Append('|');
builder.Append(createdAt.ToString("O", CultureInfo.InvariantCulture)).Append('|');
foreach (var evt in events.OrderBy(e => e.EventId, StringComparer.Ordinal))
{

View File

@@ -1,3 +1,4 @@
using System.Globalization;
using System.Text.Json;
using StellaOps.Policy.Engine.Orchestration;
@@ -55,7 +56,7 @@ internal sealed class LedgerExportService
AdvisoryId: item.AdvisoryId,
Status: item.Status,
TraceRef: item.TraceRef,
OccurredAt: result.CompletedAt.ToString("O")));
OccurredAt: result.CompletedAt.ToString("O", CultureInfo.InvariantCulture)));
}
}
@@ -66,7 +67,7 @@ internal sealed class LedgerExportService
.ThenBy(r => r.AdvisoryId, StringComparer.Ordinal)
.ToList();
var generatedAt = _timeProvider.GetUtcNow().ToString("O");
var generatedAt = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture);
var exportId = StableIdGenerator.CreateUlid($"{request.TenantId}|{generatedAt}|{ordered.Count}");
var recordLines = ordered.Select(r => JsonSerializer.Serialize(r, SerializerOptions)).ToList();

View File

@@ -1,3 +1,4 @@
using System.Globalization;
using System.Text;
namespace StellaOps.Policy.Engine.Orchestration;
@@ -95,7 +96,7 @@ internal sealed class OrchestratorJobService
.Append(request.ContextId).Append('|')
.Append(request.PolicyProfileHash).Append('|')
.Append(priority).Append('|')
.Append(requestedAt.ToString("O"));
.Append(requestedAt.ToString("O", CultureInfo.InvariantCulture));
foreach (var item in items)
{

View File

@@ -1,3 +1,4 @@
using System.Globalization;
using System.Text.Json;
namespace StellaOps.Policy.Engine.Overlay;
@@ -36,7 +37,7 @@ internal sealed class OverlayChangeEventPublisher
string correlationId,
PathDecisionDelta? delta = null)
{
var emittedAt = _timeProvider.GetUtcNow().ToString("O");
var emittedAt = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture);
return new OverlayChangeEvent(
Tenant: tenant,
RuleId: projection.RuleId,

View File

@@ -1,3 +1,4 @@
using System.Globalization;
using System.Text.Json;
using StellaOps.Policy.Engine.Services;
using StellaOps.Policy.Engine.Streaming;
@@ -40,7 +41,7 @@ internal sealed class OverlayProjectionService
var projections = new List<OverlayProjection>(orderedTargets.Count);
var version = 1;
var effectiveAt = _timeProvider.GetUtcNow().ToString("O");
var effectiveAt = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture);
foreach (var target in orderedTargets)
{

View File

@@ -197,7 +197,7 @@ public sealed class DeterminizationRuleSet
ReevalAfter = TimeSpan.FromDays(3),
Notes = $"Strict guardrails: entropy={ctx.UncertaintyScore.Entropy:F2}, trust={ctx.TrustScore:F2}, env={ctx.Environment}"
},
_ => GuardRails.Default()
_ => GuardRails.Default
};
}

View File

@@ -1,3 +1,4 @@
using System.Globalization;
using Microsoft.Extensions.Logging;
using StellaOps.Policy.RiskProfile.Scope;
@@ -155,7 +156,7 @@ internal sealed class EffectivePolicyAuditor : IEffectivePolicyAuditor
var scope = new Dictionary<string, object?>
{
["event"] = eventType,
["timestamp"] = _timeProvider.GetUtcNow().ToString("O")
["timestamp"] = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture)
};
if (!string.IsNullOrWhiteSpace(actorId))

View File

@@ -1,3 +1,4 @@
using System.Globalization;
using StellaOps.Policy.Engine.Ledger;
using StellaOps.Policy.Engine.Orchestration;
@@ -40,7 +41,7 @@ internal sealed class SnapshotService
.GroupBy(r => r.Status)
.ToDictionary(g => g.Key, g => g.Count(), StringComparer.Ordinal);
var generatedAt = _timeProvider.GetUtcNow().ToString("O");
var generatedAt = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture);
var snapshotId = StableIdGenerator.CreateUlid($"{export.Manifest.ExportId}|{request.OverlayHash}");
var snapshot = new SnapshotDetail(

View File

@@ -1,4 +1,5 @@
using System.Diagnostics;
using System.Globalization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using OpenTelemetry.Trace;
@@ -62,7 +63,7 @@ public sealed class IncidentModeService
_logger.LogWarning(
"Incident mode ENABLED. Reason: {Reason}, ExpiresAt: {ExpiresAt}",
reason,
expiresAt?.ToString("O") ?? "never");
expiresAt?.ToString("O", CultureInfo.InvariantCulture) ?? "never");
PolicyEngineTelemetry.RecordError("incident_mode_enabled", null);
}

View File

@@ -1,3 +1,4 @@
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
@@ -48,7 +49,7 @@ internal sealed class TrustWeightingService
private IReadOnlyList<TrustWeightingEntry> Normalize(IReadOnlyList<TrustWeightingEntry> entries)
{
var now = _timeProvider.GetUtcNow().ToString("O");
var now = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture);
var normalized = entries
.Where(e => !string.IsNullOrWhiteSpace(e.Source))
@@ -65,7 +66,7 @@ internal sealed class TrustWeightingService
private static IReadOnlyList<TrustWeightingEntry> DefaultWeights()
{
var now = TimeProvider.System.GetUtcNow().ToString("O");
var now = TimeProvider.System.GetUtcNow().ToString("O", CultureInfo.InvariantCulture);
return new[]
{
new TrustWeightingEntry("cartographer", 1.000m, null, now),