Determinism Gates

Audience: engineers writing or reviewing unit tests that assert reproducible output for Stella Ops artifacts.

Determinism is a core principle of Stella Ops: all artifact generation (SBOM, VEX, policy verdicts, attestations) must be reproducible. This guide shows how to assert determinism at the unit level using the DeterminismGate helpers and the deterministic time/random utilities in the TestKit. For the manifest-and-baseline workflow that gates artifact drift in CI, see Determinism Verification; for the end-to-end pipeline reproducibility suite, see E2E Reproducibility.

Why Determinism Matters

Using Determinism Gates

Reference the StellaOps.Testing.Determinism library from your test project and use the DeterminismGate static class. Tag determinism tests with the canonical Category trait (TestCategories.Determinism) so they route under the determinism CI lane — there are no [UnitTest] / [DeterminismTest] attribute types; categorization is always via [Trait("Category", ...)].

using StellaOps.Testing.Determinism;
using StellaOps.TestKit; // TestCategories
using Xunit;

public class SbomGeneratorTests
{
    [Fact]
    [Trait("Category", TestCategories.Determinism)]
    public void SbomGenerationIsDeterministic()
    {
        // Verify that calling the function 3 times produces identical output
        DeterminismGate.AssertDeterministic(() =>
        {
            return GenerateSbom();
        }, iterations: 3);
    }

    [Fact]
    [Trait("Category", TestCategories.Determinism)]
    public void SbomBinaryIsDeterministic()
    {
        // Verify binary reproducibility
        DeterminismGate.AssertDeterministic(() =>
        {
            return GenerateSbomBytes();
        }, iterations: 3);
    }
}

JSON Determinism

JSON output must have:

[Fact]
[Trait("Category", TestCategories.Determinism)]
public void VexDocumentJsonIsDeterministic()
{
    // Verifies JSON canonicalization and property ordering
    DeterminismGate.AssertJsonDeterministic(() =>
    {
        var vex = GenerateVexDocument();
        return JsonSerializer.Serialize(vex);
    });
}

[Fact]
[Trait("Category", TestCategories.Determinism)]
public void VerdictObjectIsDeterministic()
{
    // Verifies object serialization is deterministic
    DeterminismGate.AssertJsonDeterministic(() =>
    {
        return GenerateVerdict();
    });
}

Canonical Equality

Compare two objects for canonical equivalence:

[Fact]
[Trait("Category", TestCategories.Unit)]
public void VerdictFromDifferentPathsAreCanonicallyEqual()
{
    var verdict1 = GenerateVerdictFromSbom();
    var verdict2 = GenerateVerdictFromCache();

    // Asserts that both produce identical canonical JSON
    DeterminismGate.AssertCanonicallyEqual(verdict1, verdict2);
}

Hash-Based Regression Testing

Compute stable hashes for regression detection:

[Fact]
[Trait("Category", TestCategories.Determinism)]
public void SbomHashMatchesBaseline()
{
    var sbom = GenerateSbom();
    var hash = DeterminismGate.ComputeHash(sbom);

    // This hash should NEVER change unless SBOM format changes intentionally
    const string expectedHash = "abc123...";
    Assert.Equal(expectedHash, hash);
}

Path Ordering

File paths in manifests must be sorted:

[Fact]
[Trait("Category", TestCategories.Determinism)]
public void SbomFilePathsAreSorted()
{
    var sbom = GenerateSbom();
    var filePaths = ExtractFilePaths(sbom);

    // Asserts paths are in deterministic (lexicographic) order
    DeterminismGate.AssertSortedPaths(filePaths);
}

Timestamp Validation

All timestamps must be UTC ISO 8601:

[Fact]
[Trait("Category", TestCategories.Determinism)]
public void AttestationTimestampIsUtcIso8601()
{
    var attestation = GenerateAttestation();

    // Asserts timestamp is UTC with 'Z' suffix
    DeterminismGate.AssertUtcIso8601(attestation.Timestamp);
}

Deterministic Time in Tests

Use DeterministicTime for reproducible timestamps. It is constructed from a fixed UTC instant and exposes a stable UtcNow:

using StellaOps.TestKit.Deterministic;

[Fact]
[Trait("Category", TestCategories.Determinism)]
public void AttestationWithDeterministicTime()
{
    using var clock = new DeterministicTime(
        new DateTime(2026, 1, 15, 10, 30, 0, DateTimeKind.Utc));

    // All operations reading this clock get the same time
    var attestation1 = GenerateAttestation(clock.UtcNow);
    var attestation2 = GenerateAttestation(clock.UtcNow);

    Assert.Equal(attestation1.Timestamp, attestation2.Timestamp);
}

Deterministic Random in Tests

Use DeterministicRandom for reproducible randomness. The same seed always produces the same sequence:

using StellaOps.TestKit.Deterministic;

[Fact]
[Trait("Category", TestCategories.Determinism)]
public void GeneratedIdsAreReproducible()
{
    var rng1 = new DeterministicRandom(seed: 42);
    var id1 = GenerateId(rng1);

    var rng2 = new DeterministicRandom(seed: 42);
    var id2 = GenerateId(rng2);

    // Same seed → same output
    Assert.Equal(id1, id2);
}

Module-Specific Gates

Scanner Determinism

Concelier Determinism

Excititor Determinism

Policy Determinism

Attestor Determinism

Common Determinism Violations

Timestamps from system clock

// Bad: Uses system time
var timestamp = DateTimeOffset.UtcNow.ToString("o");

// Good: Uses injected clock
var timestamp = clock.UtcNow.ToString("o");

Random GUIDs

// Bad: Non-deterministic
var id = Guid.NewGuid().ToString();

// Good: Deterministic or content-addressed
var id = ComputeContentHash(data);

Unordered collections

// Bad: Dictionary iteration order is undefined
foreach (var (key, value) in dict) { ... }

// Good: Explicit ordering
foreach (var (key, value) in dict.OrderBy(x => x.Key)) { ... }

Floating-point comparisons

// Bad: Floating-point can differ across platforms
var score = 0.1 + 0.2;  // Might not equal 0.3 exactly

// Good: Use fixed-point or integers
var scoreInt = (int)((0.1 + 0.2) * 1000);

Non-UTC timestamps

// Bad: Timezone-dependent
var timestamp = DateTime.Now.ToString();

// Good: Always UTC with 'Z'
var timestamp = DateTimeOffset.UtcNow.ToString("o");

Determinism Test Checklist

When writing determinism tests, verify: