using System.Security.Cryptography; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; namespace StellaOps.Policy.Engine.Telemetry; /// /// Represents an evaluation evidence bundle containing all inputs, outputs, /// and metadata for a policy evaluation run. /// public sealed class EvidenceBundle { /// /// Unique identifier for this evidence bundle. /// public required string BundleId { get; init; } /// /// Run identifier this bundle is associated with. /// public required string RunId { get; init; } /// /// Tenant identifier. /// public required string Tenant { get; init; } /// /// Policy identifier. /// public required string PolicyId { get; init; } /// /// Policy version. /// public required string PolicyVersion { get; init; } /// /// Timestamp when the bundle was created. /// public required DateTimeOffset CreatedAt { get; init; } /// /// SHA-256 hash of the bundle contents for integrity verification. /// public string? ContentHash { get; set; } /// /// Determinism hash from the evaluation run. /// public string? DeterminismHash { get; init; } /// /// Input references for the evaluation. /// public required EvidenceInputs Inputs { get; init; } /// /// Output summary from the evaluation. /// public required EvidenceOutputs Outputs { get; init; } /// /// Environment and configuration metadata. /// public required EvidenceEnvironment Environment { get; init; } /// /// Manifest listing all artifacts in the bundle. /// public required EvidenceManifest Manifest { get; init; } } /// /// References to inputs used in the policy evaluation. /// public sealed class EvidenceInputs { /// /// SBOM document references with content hashes. /// public List SbomRefs { get; init; } = new(); /// /// Advisory document references from Concelier. /// public List AdvisoryRefs { get; init; } = new(); /// /// VEX document references from Excititor. /// public List VexRefs { get; init; } = new(); /// /// Reachability evidence references. /// public List ReachabilityRefs { get; init; } = new(); /// /// Policy pack IR digest. /// public string? PolicyIrDigest { get; init; } /// /// Cursor positions for incremental evaluation. /// public Dictionary Cursors { get; init; } = new(); } /// /// Summary of evaluation outputs. /// public sealed class EvidenceOutputs { /// /// Total findings evaluated. /// public int TotalFindings { get; init; } /// /// Findings by verdict status. /// public Dictionary FindingsByVerdict { get; init; } = new(); /// /// Findings by severity. /// public Dictionary FindingsBySeverity { get; init; } = new(); /// /// Total rules evaluated. /// public int RulesEvaluated { get; init; } /// /// Total rules that fired. /// public int RulesFired { get; init; } /// /// VEX overrides applied. /// public int VexOverridesApplied { get; init; } /// /// Duration of the evaluation in seconds. /// public double DurationSeconds { get; init; } /// /// Outcome of the evaluation (success, failure, canceled). /// public required string Outcome { get; init; } /// /// Error details if outcome is failure. /// public string? ErrorDetails { get; init; } } /// /// Environment and configuration metadata for the evaluation. /// public sealed class EvidenceEnvironment { /// /// Policy Engine service version. /// public required string ServiceVersion { get; init; } /// /// Evaluation mode (full, incremental, simulate). /// public required string Mode { get; init; } /// /// Whether sealed/air-gapped mode was active. /// public bool SealedMode { get; init; } /// /// Host machine identifier. /// public string? HostId { get; init; } /// /// Trace ID for correlation. /// public string? TraceId { get; init; } /// /// Configuration snapshot relevant to the evaluation. /// public Dictionary ConfigSnapshot { get; init; } = new(); } /// /// Manifest listing all artifacts in the evidence bundle. /// public sealed class EvidenceManifest { /// /// Version of the manifest schema. /// public string SchemaVersion { get; init; } = "1.0.0"; /// /// List of artifacts in the bundle. /// public List Artifacts { get; init; } = new(); /// /// Adds an artifact to the manifest. /// public void AddArtifact(string name, string mediaType, long sizeBytes, string contentHash) { Artifacts.Add(new EvidenceArtifact { Name = name, MediaType = mediaType, SizeBytes = sizeBytes, ContentHash = contentHash, }); } } /// /// Reference to an external artifact used as input. /// public sealed class EvidenceArtifactRef { /// /// URI or identifier for the artifact. /// public required string Uri { get; init; } /// /// Content hash (SHA-256) of the artifact. /// public required string ContentHash { get; init; } /// /// Media type of the artifact. /// public string? MediaType { get; init; } /// /// Timestamp when the artifact was fetched. /// public DateTimeOffset? FetchedAt { get; init; } } /// /// An artifact included in the evidence bundle. /// public sealed class EvidenceArtifact { /// /// Name/path of the artifact within the bundle. /// public required string Name { get; init; } /// /// Media type of the artifact. /// public required string MediaType { get; init; } /// /// Size in bytes. /// public long SizeBytes { get; init; } /// /// SHA-256 content hash. /// public required string ContentHash { get; init; } } /// /// Service for creating and managing evaluation evidence bundles. /// public sealed class EvidenceBundleService { private readonly TimeProvider _timeProvider; public EvidenceBundleService(TimeProvider timeProvider) { _timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider)); } /// /// Creates a new evidence bundle for a policy evaluation run. /// public EvidenceBundle CreateBundle( string runId, string tenant, string policyId, string policyVersion, string mode, string serviceVersion, bool sealedMode = false, string? traceId = null) { var bundleId = GenerateBundleId(runId); return new EvidenceBundle { BundleId = bundleId, RunId = runId, Tenant = tenant, PolicyId = policyId, PolicyVersion = policyVersion, CreatedAt = _timeProvider.GetUtcNow(), Inputs = new EvidenceInputs(), Outputs = new EvidenceOutputs { Outcome = "pending" }, Environment = new EvidenceEnvironment { ServiceVersion = serviceVersion, Mode = mode, SealedMode = sealedMode, TraceId = traceId, HostId = Environment.MachineName, }, Manifest = new EvidenceManifest(), }; } /// /// Finalizes the bundle by computing the content hash. /// public void FinalizeBundle(EvidenceBundle bundle) { ArgumentNullException.ThrowIfNull(bundle); var json = JsonSerializer.Serialize(bundle, EvidenceBundleJsonContext.Default.EvidenceBundle); var hash = SHA256.HashData(Encoding.UTF8.GetBytes(json)); bundle.ContentHash = Convert.ToHexStringLower(hash); } /// /// Serializes the bundle to JSON. /// public string SerializeBundle(EvidenceBundle bundle) { ArgumentNullException.ThrowIfNull(bundle); return JsonSerializer.Serialize(bundle, EvidenceBundleJsonContext.Default.EvidenceBundle); } /// /// Deserializes a bundle from JSON. /// public EvidenceBundle? DeserializeBundle(string json) { ArgumentException.ThrowIfNullOrWhiteSpace(json); return JsonSerializer.Deserialize(json, EvidenceBundleJsonContext.Default.EvidenceBundle); } private static string GenerateBundleId(string runId) { var timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); return $"bundle-{runId}-{timestamp:x}"; } } [JsonSerializable(typeof(EvidenceBundle))] [JsonSerializable(typeof(EvidenceInputs))] [JsonSerializable(typeof(EvidenceOutputs))] [JsonSerializable(typeof(EvidenceEnvironment))] [JsonSerializable(typeof(EvidenceManifest))] [JsonSerializable(typeof(EvidenceArtifact))] [JsonSerializable(typeof(EvidenceArtifactRef))] [JsonSourceGenerationOptions( WriteIndented = true, PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] internal partial class EvidenceBundleJsonContext : JsonSerializerContext { }