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
{
}