Files
git.stella-ops.org/docs/technical/testing/determinism-gates.md
2026-01-07 10:23:21 +02:00

292 lines
6.8 KiB
Markdown

# Determinism Gates
Determinism is a core principle of StellaOps - all artifact generation (SBOM, VEX, attestations) must be reproducible. This document describes how to test for determinism.
## Why Determinism Matters
- **Reproducible builds**: Same input → same output, always
- **Cryptographic verification**: Hash-based integrity depends on byte-for-byte reproducibility
- **Audit trails**: Deterministic timestamps and ordering for compliance
- **Offline operation**: No reliance on external randomness or timestamps
## Using Determinism Gates
Add `StellaOps.TestKit` to your test project and use the `DeterminismGate` class:
```csharp
using StellaOps.TestKit.Determinism;
using StellaOps.TestKit.Traits;
using Xunit;
public class SbomGeneratorTests
{
[Fact]
[UnitTest]
[DeterminismTest]
public void SbomGenerationIsDeterministic()
{
// Verify that calling the function 3 times produces identical output
DeterminismGate.AssertDeterministic(() =>
{
return GenerateSbom();
}, iterations: 3);
}
[Fact]
[UnitTest]
[DeterminismTest]
public void SbomBinaryIsDeterministic()
{
// Verify binary reproducibility
DeterminismGate.AssertDeterministic(() =>
{
return GenerateSbomBytes();
}, iterations: 3);
}
}
```
## JSON Determinism
JSON output must have:
- Stable property ordering (alphabetical or schema-defined)
- Consistent whitespace/formatting
- No random IDs or timestamps (unless explicitly from deterministic clock)
```csharp
[Fact]
[UnitTest]
[DeterminismTest]
public void VexDocumentJsonIsDeterministic()
{
// Verifies JSON canonicalization and property ordering
DeterminismGate.AssertJsonDeterministic(() =>
{
var vex = GenerateVexDocument();
return JsonSerializer.Serialize(vex);
});
}
[Fact]
[UnitTest]
[DeterminismTest]
public void VerdictObjectIsDeterministic()
{
// Verifies object serialization is deterministic
DeterminismGate.AssertJsonDeterministic(() =>
{
return GenerateVerdict();
});
}
```
## Canonical Equality
Compare two objects for canonical equivalence:
```csharp
[Fact]
[UnitTest]
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:
```csharp
[Fact]
[UnitTest]
[DeterminismTest]
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:
```csharp
[Fact]
[UnitTest]
[DeterminismTest]
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:
```csharp
[Fact]
[UnitTest]
[DeterminismTest]
public void AttestationTimestampIsUtcIso8601()
{
var attestation = GenerateAttestation();
// Asserts timestamp is UTC with 'Z' suffix
DeterminismGate.AssertUtcIso8601(attestation.Timestamp);
}
```
## Determin
istic Time in Tests
Use `DeterministicClock` for reproducible timestamps:
```csharp
using StellaOps.TestKit.Time;
[Fact]
[UnitTest]
[DeterminismTest]
public void AttestationWithDeterministicTime()
{
var clock = new DeterministicClock();
// All operations using this clock will get the same time
var attestation1 = GenerateAttestation(clock);
var attestation2 = GenerateAttestation(clock);
Assert.Equal(attestation1.Timestamp, attestation2.Timestamp);
}
```
## Deterministic Random in Tests
Use `DeterministicRandom` for reproducible randomness:
```csharp
using StellaOps.TestKit.Random;
[Fact]
[UnitTest]
[DeterminismTest]
public void GeneratedIdsAreReproducible()
{
var rng1 = DeterministicRandomExtensions.WithTestSeed();
var id1 = GenerateId(rng1);
var rng2 = DeterministicRandomExtensions.WithTestSeed();
var id2 = GenerateId(rng2);
// Same seed → same output
Assert.Equal(id1, id2);
}
```
## Module-Specific Gates
### Scanner Determinism
- SBOM file path ordering
- Component hash stability
- Dependency graph ordering
### Concelier Determinism
- Advisory normalization (same advisory → same canonical form)
- Vulnerability merge determinism
- No lattice ordering dependencies
### Excititor Determinism
- VEX document format stability
- Preserve/prune decision ordering
- No lattice dependencies
### Policy Determinism
- Verdict reproducibility (same inputs → same verdict)
- Policy evaluation ordering
- Unknown budget calculations
### Attestor Determinism
- DSSE envelope canonical bytes
- Signature ordering (multiple signers)
- Rekor receipt stability
## Common Determinism Violations
**Timestamps from system clock**
```csharp
// Bad: Uses system time
var timestamp = DateTimeOffset.UtcNow.ToString("o");
// Good: Uses injected clock
var timestamp = clock.UtcNow.ToString("o");
```
**Random GUIDs**
```csharp
// Bad: Non-deterministic
var id = Guid.NewGuid().ToString();
// Good: Deterministic or content-addressed
var id = ComputeContentHash(data);
```
**Unordered collections**
```csharp
// 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**
```csharp
// 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**
```csharp
// 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:
- [ ] Multiple invocations produce identical output
- [ ] JSON has stable property ordering
- [ ] File paths are sorted lexicographically
- [ ] Timestamps are UTC ISO 8601 with 'Z' suffix
- [ ] No random GUIDs (use content-addressing)
- [ ] Collections are explicitly ordered
- [ ] No system time/random usage (use DeterministicClock/DeterministicRandom)
## Related Documentation
- TestKit README: `src/__Libraries/StellaOps.TestKit/README.md`
- Testing Strategy: `docs/technical/testing/testing-strategy-models.md`
- Test Catalog: `docs/technical/testing/TEST_CATALOG.yml`