Files
git.stella-ops.org/src/__Libraries/StellaOps.Testing.Determinism/Determinism/DeterminismManifest.cs
master 491e883653 Add tests for SBOM generation determinism across multiple formats
- Created `StellaOps.TestKit.Tests` project for unit tests related to determinism.
- Implemented `DeterminismManifestTests` to validate deterministic output for canonical bytes and strings, file read/write operations, and error handling for invalid schema versions.
- Added `SbomDeterminismTests` to ensure identical inputs produce consistent SBOMs across SPDX 3.0.1 and CycloneDX 1.6/1.7 formats, including parallel execution tests.
- Updated project references in `StellaOps.Integration.Determinism` to include the new determinism testing library.
2025-12-24 00:36:14 +02:00

323 lines
8.8 KiB
C#

using System.Text.Json.Serialization;
namespace StellaOps.Testing.Determinism;
/// <summary>
/// Determinism manifest tracking artifact reproducibility with canonical bytes hash,
/// version stamps, and toolchain information.
/// </summary>
public sealed record DeterminismManifest
{
/// <summary>
/// Version of this manifest schema (currently "1.0").
/// </summary>
[JsonPropertyName("schemaVersion")]
public required string SchemaVersion { get; init; }
/// <summary>
/// Artifact being tracked for determinism.
/// </summary>
[JsonPropertyName("artifact")]
public required ArtifactInfo Artifact { get; init; }
/// <summary>
/// Hash of the canonical representation of the artifact.
/// </summary>
[JsonPropertyName("canonicalHash")]
public required CanonicalHashInfo CanonicalHash { get; init; }
/// <summary>
/// Version stamps of all inputs used to generate the artifact.
/// </summary>
[JsonPropertyName("inputs")]
public InputStamps? Inputs { get; init; }
/// <summary>
/// Toolchain version information.
/// </summary>
[JsonPropertyName("toolchain")]
public required ToolchainInfo Toolchain { get; init; }
/// <summary>
/// UTC timestamp when artifact was generated (ISO 8601).
/// </summary>
[JsonPropertyName("generatedAt")]
public required DateTimeOffset GeneratedAt { get; init; }
/// <summary>
/// Reproducibility metadata.
/// </summary>
[JsonPropertyName("reproducibility")]
public ReproducibilityMetadata? Reproducibility { get; init; }
/// <summary>
/// Verification instructions for reproducing the artifact.
/// </summary>
[JsonPropertyName("verification")]
public VerificationInfo? Verification { get; init; }
/// <summary>
/// Optional cryptographic signatures of this manifest.
/// </summary>
[JsonPropertyName("signatures")]
public IReadOnlyList<SignatureInfo>? Signatures { get; init; }
}
/// <summary>
/// Artifact being tracked for determinism.
/// </summary>
public sealed record ArtifactInfo
{
/// <summary>
/// Type of artifact.
/// </summary>
[JsonPropertyName("type")]
public required string Type { get; init; }
/// <summary>
/// Artifact identifier or name.
/// </summary>
[JsonPropertyName("name")]
public required string Name { get; init; }
/// <summary>
/// Artifact version or timestamp.
/// </summary>
[JsonPropertyName("version")]
public required string Version { get; init; }
/// <summary>
/// Artifact format (e.g., 'SPDX 3.0.1', 'CycloneDX 1.6', 'OpenVEX').
/// </summary>
[JsonPropertyName("format")]
public string? Format { get; init; }
/// <summary>
/// Additional artifact-specific metadata.
/// </summary>
[JsonPropertyName("metadata")]
public IReadOnlyDictionary<string, object?>? Metadata { get; init; }
}
/// <summary>
/// Hash of the canonical representation of the artifact.
/// </summary>
public sealed record CanonicalHashInfo
{
/// <summary>
/// Hash algorithm used (SHA-256, SHA-384, SHA-512).
/// </summary>
[JsonPropertyName("algorithm")]
public required string Algorithm { get; init; }
/// <summary>
/// Hex-encoded hash value.
/// </summary>
[JsonPropertyName("value")]
public required string Value { get; init; }
/// <summary>
/// Encoding of the hash value (hex or base64).
/// </summary>
[JsonPropertyName("encoding")]
public required string Encoding { get; init; }
}
/// <summary>
/// Version stamps of all inputs used to generate the artifact.
/// </summary>
public sealed record InputStamps
{
/// <summary>
/// SHA-256 hash of the vulnerability feed snapshot used.
/// </summary>
[JsonPropertyName("feedSnapshotHash")]
public string? FeedSnapshotHash { get; init; }
/// <summary>
/// SHA-256 hash of the policy manifest used.
/// </summary>
[JsonPropertyName("policyManifestHash")]
public string? PolicyManifestHash { get; init; }
/// <summary>
/// Git commit SHA or source code hash.
/// </summary>
[JsonPropertyName("sourceCodeHash")]
public string? SourceCodeHash { get; init; }
/// <summary>
/// Hash of dependency lockfile (e.g., package-lock.json, Cargo.lock).
/// </summary>
[JsonPropertyName("dependencyLockfileHash")]
public string? DependencyLockfileHash { get; init; }
/// <summary>
/// Container base image digest (sha256:...).
/// </summary>
[JsonPropertyName("baseImageDigest")]
public string? BaseImageDigest { get; init; }
/// <summary>
/// Hashes of all VEX documents used as input.
/// </summary>
[JsonPropertyName("vexDocumentHashes")]
public IReadOnlyList<string>? VexDocumentHashes { get; init; }
/// <summary>
/// Custom input hashes specific to artifact type.
/// </summary>
[JsonPropertyName("custom")]
public IReadOnlyDictionary<string, string>? Custom { get; init; }
}
/// <summary>
/// Toolchain version information.
/// </summary>
public sealed record ToolchainInfo
{
/// <summary>
/// Runtime platform (e.g., '.NET 10.0', 'Node.js 20.0').
/// </summary>
[JsonPropertyName("platform")]
public required string Platform { get; init; }
/// <summary>
/// Toolchain component versions.
/// </summary>
[JsonPropertyName("components")]
public required IReadOnlyList<ComponentInfo> Components { get; init; }
/// <summary>
/// Compiler information if applicable.
/// </summary>
[JsonPropertyName("compiler")]
public CompilerInfo? Compiler { get; init; }
}
/// <summary>
/// Toolchain component version.
/// </summary>
public sealed record ComponentInfo
{
/// <summary>
/// Component name (e.g., 'StellaOps.Scanner', 'CycloneDX Generator').
/// </summary>
[JsonPropertyName("name")]
public required string Name { get; init; }
/// <summary>
/// Semantic version or git SHA.
/// </summary>
[JsonPropertyName("version")]
public required string Version { get; init; }
/// <summary>
/// Optional: SHA-256 hash of the component binary.
/// </summary>
[JsonPropertyName("hash")]
public string? Hash { get; init; }
}
/// <summary>
/// Compiler information.
/// </summary>
public sealed record CompilerInfo
{
/// <summary>
/// Compiler name (e.g., 'Roslyn', 'rustc').
/// </summary>
[JsonPropertyName("name")]
public required string Name { get; init; }
/// <summary>
/// Compiler version.
/// </summary>
[JsonPropertyName("version")]
public required string Version { get; init; }
}
/// <summary>
/// Reproducibility metadata.
/// </summary>
public sealed record ReproducibilityMetadata
{
/// <summary>
/// Deterministic random seed if used.
/// </summary>
[JsonPropertyName("deterministicSeed")]
public int? DeterministicSeed { get; init; }
/// <summary>
/// Whether system clock was fixed during generation.
/// </summary>
[JsonPropertyName("clockFixed")]
public bool? ClockFixed { get; init; }
/// <summary>
/// Ordering guarantee for collections in output.
/// </summary>
[JsonPropertyName("orderingGuarantee")]
public string? OrderingGuarantee { get; init; }
/// <summary>
/// Normalization rules applied (e.g., 'UTF-8', 'LF line endings', 'no whitespace').
/// </summary>
[JsonPropertyName("normalizationRules")]
public IReadOnlyList<string>? NormalizationRules { get; init; }
}
/// <summary>
/// Verification instructions for reproducing the artifact.
/// </summary>
public sealed record VerificationInfo
{
/// <summary>
/// Command to regenerate the artifact.
/// </summary>
[JsonPropertyName("command")]
public string? Command { get; init; }
/// <summary>
/// Expected SHA-256 hash after reproduction.
/// </summary>
[JsonPropertyName("expectedHash")]
public string? ExpectedHash { get; init; }
/// <summary>
/// Baseline manifest file path for regression testing.
/// </summary>
[JsonPropertyName("baseline")]
public string? Baseline { get; init; }
}
/// <summary>
/// Cryptographic signature of the manifest.
/// </summary>
public sealed record SignatureInfo
{
/// <summary>
/// Signature algorithm (e.g., 'ES256', 'RS256').
/// </summary>
[JsonPropertyName("algorithm")]
public required string Algorithm { get; init; }
/// <summary>
/// Key identifier used for signing.
/// </summary>
[JsonPropertyName("keyId")]
public required string KeyId { get; init; }
/// <summary>
/// Base64-encoded signature.
/// </summary>
[JsonPropertyName("signature")]
public required string Signature { get; init; }
/// <summary>
/// UTC timestamp when signature was created.
/// </summary>
[JsonPropertyName("timestamp")]
public DateTimeOffset? Timestamp { get; init; }
}