189 lines
7.1 KiB
Markdown
189 lines
7.1 KiB
Markdown
# Evidence Reconciliation
|
||
|
||
This document describes the evidence reconciliation algorithm implemented in the `StellaOps.AirGap.Importer` module. The algorithm provides deterministic, lattice-based reconciliation of security evidence from air-gapped bundles.
|
||
|
||
## Overview
|
||
|
||
Evidence reconciliation is a 5-step pipeline that transforms raw evidence artifacts (SBOMs, attestations, VEX documents) into a unified, content-addressed evidence graph suitable for policy evaluation and audit trails.
|
||
|
||
## Architecture
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ Evidence Reconciliation Pipeline │
|
||
├─────────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ Step 1: Artifact Indexing │
|
||
│ ├── EvidenceDirectoryDiscovery │
|
||
│ ├── ArtifactIndex (digest-keyed) │
|
||
│ └── Digest normalization (sha256:...) │
|
||
│ │
|
||
│ Step 2: Evidence Collection │
|
||
│ ├── SbomCollector (CycloneDX, SPDX) │
|
||
│ ├── AttestationCollector (DSSE) │
|
||
│ └── Integration with DsseVerifier │
|
||
│ │
|
||
│ Step 3: Normalization │
|
||
│ ├── JsonNormalizer (stable sorting) │
|
||
│ ├── Timestamp stripping │
|
||
│ └── URI lowercase normalization │
|
||
│ │
|
||
│ Step 4: Lattice Rules │
|
||
│ ├── SourcePrecedenceLattice │
|
||
│ ├── VEX merge with precedence │
|
||
│ └── Conflict resolution │
|
||
│ │
|
||
│ Step 5: Graph Emission │
|
||
│ ├── EvidenceGraph construction │
|
||
│ ├── Deterministic serialization │
|
||
│ └── SHA-256 manifest generation │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
## Components
|
||
|
||
### Step 1: Artifact Indexing
|
||
|
||
**`ArtifactIndex`** - A digest-keyed index of all artifacts in the evidence bundle.
|
||
|
||
```csharp
|
||
// Key types
|
||
public readonly record struct DigestKey(string Algorithm, string Value);
|
||
|
||
// Normalization
|
||
DigestKey.Parse("sha256:abc123...") → DigestKey("sha256", "abc123...")
|
||
```
|
||
|
||
**`EvidenceDirectoryDiscovery`** - Discovers evidence files from a directory structure.
|
||
|
||
Expected structure:
|
||
```
|
||
evidence/
|
||
├── sboms/
|
||
│ ├── component-a.cdx.json
|
||
│ └── component-b.spdx.json
|
||
├── attestations/
|
||
│ └── artifact.dsse.json
|
||
└── vex/
|
||
└── vendor-vex.json
|
||
```
|
||
|
||
### Step 2: Evidence Collection
|
||
|
||
**Parsers:**
|
||
- `CycloneDxParser` - Parses CycloneDX 1.4–1.7 format
|
||
- `SpdxParser` - Parses SPDX 2.3 format
|
||
- `DsseAttestationParser` - Parses DSSE envelopes
|
||
|
||
**Collectors:**
|
||
- `SbomCollector` - Orchestrates SBOM parsing and indexing
|
||
- `AttestationCollector` - Orchestrates attestation parsing and verification
|
||
|
||
### Step 3: Normalization
|
||
|
||
**`SbomNormalizer`** applies format-specific normalization:
|
||
|
||
| Rule | Description |
|
||
|------|-------------|
|
||
| Stable JSON sorting | Keys sorted alphabetically (ordinal) |
|
||
| Timestamp stripping | Removes `created`, `modified`, `timestamp` fields |
|
||
| URI normalization | Lowercases scheme, host, normalizes paths |
|
||
| Whitespace normalization | Consistent formatting |
|
||
|
||
### Step 4: Lattice Rules
|
||
|
||
**`SourcePrecedenceLattice`** implements a bounded lattice for VEX source authority:
|
||
|
||
```
|
||
Vendor (top)
|
||
↑
|
||
Maintainer
|
||
↑
|
||
ThirdParty
|
||
↑
|
||
Unknown (bottom)
|
||
```
|
||
|
||
**Lattice Properties (verified by property-based tests):**
|
||
- **Commutativity**: `Join(a, b) = Join(b, a)`
|
||
- **Associativity**: `Join(Join(a, b), c) = Join(a, Join(b, c))`
|
||
- **Idempotence**: `Join(a, a) = a`
|
||
- **Absorption**: `Join(a, Meet(a, b)) = a`
|
||
|
||
**Conflict Resolution Order:**
|
||
1. Higher precedence source wins
|
||
2. More recent timestamp wins (when same precedence)
|
||
3. Status priority: NotAffected > Fixed > UnderInvestigation > Affected > Unknown
|
||
|
||
### Step 5: Graph Emission
|
||
|
||
**`EvidenceGraph`** - A content-addressed graph of reconciled evidence:
|
||
|
||
```csharp
|
||
public sealed record EvidenceGraph
|
||
{
|
||
public required string Version { get; init; }
|
||
public required string DigestAlgorithm { get; init; }
|
||
public required string RootDigest { get; init; }
|
||
public required IReadOnlyList<EvidenceNode> Nodes { get; init; }
|
||
public required IReadOnlyList<EvidenceEdge> Edges { get; init; }
|
||
public required DateTimeOffset GeneratedAt { get; init; }
|
||
}
|
||
```
|
||
|
||
**Determinism guarantees:**
|
||
- Nodes sorted by digest (ordinal)
|
||
- Edges sorted by (source, target, type)
|
||
- SHA-256 manifest includes content hash
|
||
- Reproducible across runs with same inputs
|
||
|
||
## Integration
|
||
|
||
### CLI Usage
|
||
|
||
```bash
|
||
# Verify offline evidence bundle
|
||
stellaops verify offline \
|
||
--evidence-dir /evidence \
|
||
--artifact sha256:def456... \
|
||
--policy verify-policy.yaml
|
||
```
|
||
|
||
### API
|
||
|
||
```csharp
|
||
// Reconcile evidence
|
||
var reconciler = new EvidenceReconciler(options);
|
||
var graph = await reconciler.ReconcileAsync(evidenceDir, cancellationToken);
|
||
|
||
// Verify determinism
|
||
var hash1 = graph.ComputeHash();
|
||
var graph2 = await reconciler.ReconcileAsync(evidenceDir, cancellationToken);
|
||
var hash2 = graph2.ComputeHash();
|
||
Debug.Assert(hash1 == hash2); // Always true
|
||
```
|
||
|
||
## Testing
|
||
|
||
### Golden-File Tests
|
||
|
||
Test fixtures in `tests/AirGap/StellaOps.AirGap.Importer.Tests/Reconciliation/Fixtures/`:
|
||
- `cyclonedx-sample.json` - CycloneDX 1.5 sample
|
||
- `spdx-sample.json` - SPDX 2.3 sample
|
||
- `dsse-attestation-sample.json` - DSSE envelope sample
|
||
|
||
### Property-Based Tests
|
||
|
||
`SourcePrecedenceLatticePropertyTests` verifies:
|
||
- Lattice algebraic properties (commutativity, associativity, idempotence, absorption)
|
||
- Ordering properties (antisymmetry, transitivity, reflexivity)
|
||
- Bound properties (join is LUB, meet is GLB)
|
||
- Merge determinism
|
||
|
||
## Related Documents
|
||
|
||
- [Air-Gap Module Architecture](./architecture.md) *(pending)*
|
||
- [DSSE Verification](../../adr/dsse-verification.md) *(if exists)*
|
||
- [Offline Kit Import Flow](./exporter-cli-coordination.md)
|