up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled

This commit is contained in:
master
2025-11-27 15:05:48 +02:00
parent 4831c7fcb0
commit e950474a77
278 changed files with 81498 additions and 672 deletions

View File

@@ -0,0 +1,379 @@
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace StellaOps.Policy.Engine.Telemetry;
/// <summary>
/// Represents an evaluation evidence bundle containing all inputs, outputs,
/// and metadata for a policy evaluation run.
/// </summary>
public sealed class EvidenceBundle
{
/// <summary>
/// Unique identifier for this evidence bundle.
/// </summary>
public required string BundleId { get; init; }
/// <summary>
/// Run identifier this bundle is associated with.
/// </summary>
public required string RunId { get; init; }
/// <summary>
/// Tenant identifier.
/// </summary>
public required string Tenant { get; init; }
/// <summary>
/// Policy identifier.
/// </summary>
public required string PolicyId { get; init; }
/// <summary>
/// Policy version.
/// </summary>
public required string PolicyVersion { get; init; }
/// <summary>
/// Timestamp when the bundle was created.
/// </summary>
public required DateTimeOffset CreatedAt { get; init; }
/// <summary>
/// SHA-256 hash of the bundle contents for integrity verification.
/// </summary>
public string? ContentHash { get; set; }
/// <summary>
/// Determinism hash from the evaluation run.
/// </summary>
public string? DeterminismHash { get; init; }
/// <summary>
/// Input references for the evaluation.
/// </summary>
public required EvidenceInputs Inputs { get; init; }
/// <summary>
/// Output summary from the evaluation.
/// </summary>
public required EvidenceOutputs Outputs { get; init; }
/// <summary>
/// Environment and configuration metadata.
/// </summary>
public required EvidenceEnvironment Environment { get; init; }
/// <summary>
/// Manifest listing all artifacts in the bundle.
/// </summary>
public required EvidenceManifest Manifest { get; init; }
}
/// <summary>
/// References to inputs used in the policy evaluation.
/// </summary>
public sealed class EvidenceInputs
{
/// <summary>
/// SBOM document references with content hashes.
/// </summary>
public List<EvidenceArtifactRef> SbomRefs { get; init; } = new();
/// <summary>
/// Advisory document references from Concelier.
/// </summary>
public List<EvidenceArtifactRef> AdvisoryRefs { get; init; } = new();
/// <summary>
/// VEX document references from Excititor.
/// </summary>
public List<EvidenceArtifactRef> VexRefs { get; init; } = new();
/// <summary>
/// Reachability evidence references.
/// </summary>
public List<EvidenceArtifactRef> ReachabilityRefs { get; init; } = new();
/// <summary>
/// Policy pack IR digest.
/// </summary>
public string? PolicyIrDigest { get; init; }
/// <summary>
/// Cursor positions for incremental evaluation.
/// </summary>
public Dictionary<string, string> Cursors { get; init; } = new();
}
/// <summary>
/// Summary of evaluation outputs.
/// </summary>
public sealed class EvidenceOutputs
{
/// <summary>
/// Total findings evaluated.
/// </summary>
public int TotalFindings { get; init; }
/// <summary>
/// Findings by verdict status.
/// </summary>
public Dictionary<string, int> FindingsByVerdict { get; init; } = new();
/// <summary>
/// Findings by severity.
/// </summary>
public Dictionary<string, int> FindingsBySeverity { get; init; } = new();
/// <summary>
/// Total rules evaluated.
/// </summary>
public int RulesEvaluated { get; init; }
/// <summary>
/// Total rules that fired.
/// </summary>
public int RulesFired { get; init; }
/// <summary>
/// VEX overrides applied.
/// </summary>
public int VexOverridesApplied { get; init; }
/// <summary>
/// Duration of the evaluation in seconds.
/// </summary>
public double DurationSeconds { get; init; }
/// <summary>
/// Outcome of the evaluation (success, failure, canceled).
/// </summary>
public required string Outcome { get; init; }
/// <summary>
/// Error details if outcome is failure.
/// </summary>
public string? ErrorDetails { get; init; }
}
/// <summary>
/// Environment and configuration metadata for the evaluation.
/// </summary>
public sealed class EvidenceEnvironment
{
/// <summary>
/// Policy Engine service version.
/// </summary>
public required string ServiceVersion { get; init; }
/// <summary>
/// Evaluation mode (full, incremental, simulate).
/// </summary>
public required string Mode { get; init; }
/// <summary>
/// Whether sealed/air-gapped mode was active.
/// </summary>
public bool SealedMode { get; init; }
/// <summary>
/// Host machine identifier.
/// </summary>
public string? HostId { get; init; }
/// <summary>
/// Trace ID for correlation.
/// </summary>
public string? TraceId { get; init; }
/// <summary>
/// Configuration snapshot relevant to the evaluation.
/// </summary>
public Dictionary<string, string> ConfigSnapshot { get; init; } = new();
}
/// <summary>
/// Manifest listing all artifacts in the evidence bundle.
/// </summary>
public sealed class EvidenceManifest
{
/// <summary>
/// Version of the manifest schema.
/// </summary>
public string SchemaVersion { get; init; } = "1.0.0";
/// <summary>
/// List of artifacts in the bundle.
/// </summary>
public List<EvidenceArtifact> Artifacts { get; init; } = new();
/// <summary>
/// Adds an artifact to the manifest.
/// </summary>
public void AddArtifact(string name, string mediaType, long sizeBytes, string contentHash)
{
Artifacts.Add(new EvidenceArtifact
{
Name = name,
MediaType = mediaType,
SizeBytes = sizeBytes,
ContentHash = contentHash,
});
}
}
/// <summary>
/// Reference to an external artifact used as input.
/// </summary>
public sealed class EvidenceArtifactRef
{
/// <summary>
/// URI or identifier for the artifact.
/// </summary>
public required string Uri { get; init; }
/// <summary>
/// Content hash (SHA-256) of the artifact.
/// </summary>
public required string ContentHash { get; init; }
/// <summary>
/// Media type of the artifact.
/// </summary>
public string? MediaType { get; init; }
/// <summary>
/// Timestamp when the artifact was fetched.
/// </summary>
public DateTimeOffset? FetchedAt { get; init; }
}
/// <summary>
/// An artifact included in the evidence bundle.
/// </summary>
public sealed class EvidenceArtifact
{
/// <summary>
/// Name/path of the artifact within the bundle.
/// </summary>
public required string Name { get; init; }
/// <summary>
/// Media type of the artifact.
/// </summary>
public required string MediaType { get; init; }
/// <summary>
/// Size in bytes.
/// </summary>
public long SizeBytes { get; init; }
/// <summary>
/// SHA-256 content hash.
/// </summary>
public required string ContentHash { get; init; }
}
/// <summary>
/// Service for creating and managing evaluation evidence bundles.
/// </summary>
public sealed class EvidenceBundleService
{
private readonly TimeProvider _timeProvider;
public EvidenceBundleService(TimeProvider timeProvider)
{
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
}
/// <summary>
/// Creates a new evidence bundle for a policy evaluation run.
/// </summary>
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(),
};
}
/// <summary>
/// Finalizes the bundle by computing the content hash.
/// </summary>
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);
}
/// <summary>
/// Serializes the bundle to JSON.
/// </summary>
public string SerializeBundle(EvidenceBundle bundle)
{
ArgumentNullException.ThrowIfNull(bundle);
return JsonSerializer.Serialize(bundle, EvidenceBundleJsonContext.Default.EvidenceBundle);
}
/// <summary>
/// Deserializes a bundle from JSON.
/// </summary>
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
{
}