# Product Advisory: Deterministic Resolver Architecture
**Date:** 2025-12-24
**Author:** Architecture Guild
**Status:** Approved for Implementation
**Sprint Epoch:** 9100
**Priority:** P0 (Critical Path for Audit Compliance)
---
## Executive Summary
This advisory defines the architecture for a **unified deterministic resolver** that evaluates vulnerability graphs using only declared evidence edges and produces cryptographically verifiable, reproducible verdicts. The resolver guarantees: **same inputs → same traversal order → same per-node inputs → same verdicts → same output digest**.
This is a foundational capability for:
- Auditor-friendly verification ("same inputs → same verdicts")
- Offline replay by vendors/distributors
- Single-digest comparison across runs
- CI/CD gate assertions
---
## Problem Statement
Current implementation has strong building blocks but lacks unified orchestration:
| Component | Location | Gap |
|-----------|----------|-----|
| Topological Sort | `DeterministicGraphOrderer` | Traversal only; no verdict output |
| Lattice Evaluation | `TrustLatticeEngine`, `PolicyEvaluator` | No traversal sequence in output |
| Content Addressing | `ContentAddressedIdGenerator` | Node IDs only; no edge IDs |
| Purity Analysis | `ProhibitedPatternAnalyzer` | Static only; no runtime enforcement |
| Digest Computation | Various | No unified "run digest" |
**Result:** Cannot produce a single `ResolutionResult` that captures:
1. The exact traversal sequence used
2. Per-node verdicts with individual digests
3. A composite `FinalDigest` for verification
---
## Solution Architecture
### Core Data Model
```csharp
// New: src/__Libraries/StellaOps.Resolver/
///
/// Content-addressed node identifier.
/// Format: sha256(kind || ":" || normalized-key)
///
public sealed record NodeId(string Value) : IComparable
{
public int CompareTo(NodeId? other) =>
string.Compare(Value, other?.Value, StringComparison.Ordinal);
public static NodeId From(string kind, string normalizedKey) =>
new(HexSha256($"{kind}:{normalizedKey}"));
}
///
/// Content-addressed edge identifier.
/// Format: sha256(srcID || "->" || edgeKind || "->" || dstID)
///
public sealed record EdgeId(string Value) : IComparable
{
public static EdgeId From(NodeId src, string kind, NodeId dst) =>
new(HexSha256($"{src.Value}->{kind}->{dst.Value}"));
}
///
/// Graph edge with content-addressed identity and optional cycle-cut marker.
///
public sealed record Edge(
EdgeId Id,
NodeId Src,
string Kind,
NodeId Dst,
JsonElement Attrs,
bool IsCycleCut = false);
///
/// Graph node with content-addressed identity.
///
public sealed record Node(
NodeId Id,
string Kind,
JsonElement Attrs);
///
/// Immutable policy with content-addressed identity.
///
public sealed record Policy(
string Version,
JsonElement Rules,
string ConstantsDigest);
///
/// Individual verdict with its own content-addressed digest.
///
public sealed record Verdict(
NodeId Node,
string Status,
JsonElement Evidence,
string VerdictDigest);
///
/// Complete resolution result with all digests for verification.
///
public sealed record ResolutionResult(
ImmutableArray TraversalSequence,
ImmutableArray Verdicts,
string GraphDigest,
string PolicyDigest,
string FinalDigest);
```
### Resolver Algorithm
```text
normalize(graph); // IDs, fields, ordering
validate(graph); // No implicit data, cycles declared
assert no-ambient-inputs(); // Runtime purity check
order = topoSortWithLexTieBreak(graph); // Uses only evidence edges
verdicts = []
for node in order:
inbound = gatherInboundEvidence(node) // Pure, no IO
verdict = Evaluate(node, inbound, policy) // Pure, no IO
verdicts.append(canonicalize(verdict) with VerdictDigest)
result = {
traversal: order,
verdicts: verdicts,
graphDigest: digest(canonical(graph)),
policyDigest: digest(canonical(policy))
}
result.finalDigest = digest(canonical(result))
return result
```
### Key Invariants
1. **Canonical IDs**: `NodeId = sha256(kind:normalized-key)`, `EdgeId = sha256(src->kind->dst)`
2. **Canonical Serialization**: Alphabetical keys, sorted arrays, fixed numerics, ISO-8601 Z timestamps
3. **Fixed Traversal**: Kahn's algorithm with lexicographic tie-breaker (`SortedSet`)
4. **Explicit Cycles**: Cycles require `IsCycleCut = true` edge; unmarked cycles → invalid graph
5. **Evidence-Only Evaluation**: Node verdict = f(node, inbound_edges, policy) — no ambient IO
6. **Stable Outputs**: `(TraversalSequence, Verdicts[], GraphDigest, PolicyDigest, FinalDigest)`
---
## Gap Analysis vs Current Implementation
### Already Implemented (No Work Needed)
| Requirement | Implementation | Location |
|-------------|---------------|----------|
| Canonical JSON | RFC 8785 compliant | `CanonicalJsonSerializer`, `Rfc8785JsonCanonicalizer` |
| Alphabetical key sorting | `StableDictionaryConverter` | `StellaOps.Canonicalization` |
| ISO-8601 UTC timestamps | `Iso8601DateTimeConverter` | `StellaOps.Canonicalization` |
| Topological sort with lex tie-break | `SortedSet` | `DeterministicGraphOrderer:134` |
| K4 Four-Valued Lattice | Complete | `K4Lattice.cs` |
| VEX Lattice Merging | With traces | `OpenVexStatementMerger` |
| Merkle Tree Proofs | SHA256-based | `DeterministicMerkleTreeBuilder` |
| Static Purity Analysis | 16+ patterns | `ProhibitedPatternAnalyzer` |
| Injected Timestamp | Context-based | `PolicyEvaluationContext.Now` |
| Graph Content Hash | SHA256 | `DeterministicGraphOrderer.ComputeCanonicalHash()` |
### Gaps to Implement
| Gap | Priority | Sprint | Description |
|-----|----------|--------|-------------|
| Unified Resolver | P0 | 9100.0001.0001 | Single entry point producing `ResolutionResult` |
| Cycle-Cut Edges | P1 | 9100.0001.0002 | `IsCycleCut` edge property; validation |
| EdgeId | P2 | 9100.0001.0003 | Content-addressed edge identifiers |
| FinalDigest | P1 | 9100.0002.0001 | Composite run-level digest |
| Per-Node VerdictDigest | P2 | 9100.0002.0002 | Individual verdict digests |
| Runtime Purity | P1 | 9100.0003.0001 | Runtime enforcement beyond static analysis |
| Graph Validation + NFC | P3 | 9100.0003.0002 | Pre-traversal validation; NFC normalization |
---
## Implementation Phases
### Phase 1: Core Resolver Package (Sprint 9100.0001.*)
**Goal:** Create `StellaOps.Resolver` library with unified resolver pattern.
1. **Sprint 9100.0001.0001** — Core Resolver
- `DeterministicResolver` class
- `ResolutionResult` record
- Integration with existing `DeterministicGraphOrderer`
- Integration with existing `TrustLatticeEngine`
2. **Sprint 9100.0001.0002** — Cycle-Cut Edges
- `IsCycleCut` property on edges
- Cycle validation (unmarked cycles → error)
- Graph validation before traversal
3. **Sprint 9100.0001.0003** — Content-Addressed EdgeId
- `EdgeId` record
- Edge content addressing
- Merkle tree inclusion of edges
### Phase 2: Digest Infrastructure (Sprint 9100.0002.*)
**Goal:** Implement comprehensive digest chain for verification.
1. **Sprint 9100.0002.0001** — FinalDigest
- Composite digest computation
- `sha256(canonical({graphDigest, policyDigest, verdicts[]}))`
- Integration with attestation system
2. **Sprint 9100.0002.0002** — Per-Node VerdictDigest
- Verdict-level content addressing
- Drill-down debugging ("which node changed?")
- Delta detection between runs
### Phase 3: Purity & Validation (Sprint 9100.0003.*)
**Goal:** Harden determinism guarantees with runtime enforcement.
1. **Sprint 9100.0003.0001** — Runtime Purity Enforcement
- Runtime guards beyond static analysis
- Dependency injection shims for ambient services
- Test harness for purity verification
2. **Sprint 9100.0003.0002** — Graph Validation & NFC
- Pre-traversal validation ("no implicit data")
- Unicode NFC normalization for string fields
- Evidence completeness assertions
---
## Testing Requirements
### Mandatory Test Types
1. **Replay Test**: Same input twice → identical `FinalDigest`
2. **Permutation Test**: Shuffle input nodes/edges → identical outputs
3. **Cycle Test**: Introduce cycle → fail unless `IsCycleCut` edge present
4. **Ambience Test**: Forbid calls to time/env/network during evaluation
5. **Serialization Test**: Canonicalization changes → predictable digest changes
### Property-Based Tests
```csharp
// Idempotency
[Property]
public void Resolver_SameInput_SameOutput(Graph graph)
{
var result1 = resolver.Run(graph);
var result2 = resolver.Run(graph);
Assert.Equal(result1.FinalDigest, result2.FinalDigest);
}
// Order Independence
[Property]
public void Resolver_ShuffledInputs_SameOutput(Graph graph, int seed)
{
var shuffled = ShuffleNodesAndEdges(graph, seed);
var result1 = resolver.Run(graph);
var result2 = resolver.Run(shuffled);
Assert.Equal(result1.FinalDigest, result2.FinalDigest);
}
// Cycle Detection
[Property]
public void Resolver_UnmarkedCycle_Throws(Graph graphWithCycle)
{
Assume.That(!graphWithCycle.Edges.Any(e => e.IsCycleCut));
Assert.Throws(() => resolver.Run(graphWithCycle));
}
```
---
## Integration Points
### Existing Components to Integrate
| Component | Integration |
|-----------|-------------|
| `DeterministicGraphOrderer` | Use for traversal order computation |
| `TrustLatticeEngine` | Use for K4 lattice evaluation |
| `CanonicalJsonSerializer` | Use for all serialization |
| `ContentAddressedIdGenerator` | Extend for EdgeId and VerdictId |
| `ProhibitedPatternAnalyzer` | Combine with runtime guards |
| `PolicyEvaluationContext` | Use for injected inputs |
### New Modules
| Module | Purpose |
|--------|---------|
| `StellaOps.Resolver` | Core resolver library |
| `StellaOps.Resolver.Testing` | Test fixtures and harnesses |
| `StellaOps.Resolver.Attestation` | Integration with proof chain |
---
## Success Criteria
1. **Unified API**: Single `resolver.Run(graph)` produces complete `ResolutionResult`
2. **Deterministic**: 100% pass rate on replay/permutation/cycle tests
3. **Auditable**: `FinalDigest` enables single-value verification
4. **Performant**: No more than 10% overhead vs current fragmented approach
5. **Documented**: Full API documentation and integration guide
---
## Appendix: C# Reference Implementation
```csharp
public sealed class DeterministicResolver
{
private readonly Policy _policy;
private readonly string _policyDigest;
private readonly DeterministicGraphOrderer _orderer;
private readonly TrustLatticeEngine _lattice;
private readonly CanonicalJsonSerializer _serializer;
public DeterministicResolver(
Policy policy,
DeterministicGraphOrderer orderer,
TrustLatticeEngine lattice,
CanonicalJsonSerializer serializer)
{
_policy = Canon(policy);
_policyDigest = HexSha256(serializer.SerializeToBytes(_policy));
_orderer = orderer;
_lattice = lattice;
_serializer = serializer;
}
public ResolutionResult Run(EvidenceGraph graph)
{
// 1. Canonicalize and validate
var canonical = _orderer.Canonicalize(graph);
ValidateCycles(canonical);
EnsureNoAmbientInputs();
// 2. Traverse in deterministic order
var traversal = canonical.Nodes
.Select(n => NodeId.Parse(n.Id))
.ToImmutableArray();
// 3. Evaluate each node purely
var verdicts = new List(traversal.Length);
foreach (var nodeId in traversal)
{
var node = GetNode(canonical, nodeId);
var inbound = GatherInboundEvidence(canonical, nodeId);
var verdict = EvaluatePure(node, inbound, _policy);
var verdictBytes = _serializer.SerializeToBytes(verdict);
verdicts.Add(verdict with { VerdictDigest = HexSha256(verdictBytes) });
}
// 4. Compute digests
var verdictsArray = verdicts.ToImmutableArray();
var resultWithoutFinal = new ResolutionResult(
traversal,
verdictsArray,
canonical.ContentHash,
_policyDigest,
"");
var finalBytes = _serializer.SerializeToBytes(resultWithoutFinal);
return resultWithoutFinal with { FinalDigest = HexSha256(finalBytes) };
}
private void ValidateCycles(CanonicalGraph graph)
{
// Detect cycles; require IsCycleCut for each
var cycles = DetectCycles(graph);
var unmarked = cycles.Where(c => !c.CutEdge.IsCycleCut).ToList();
if (unmarked.Any())
{
throw new InvalidGraphException(
$"Graph contains {unmarked.Count} unmarked cycle(s). " +
"Add IsCycleCut=true to break cycles.");
}
}
private Verdict EvaluatePure(Node node, InboundEvidence evidence, Policy policy)
{
// Pure evaluation: no IO, no ambient state
return _lattice.Evaluate(node, evidence, policy);
}
}
```
---
## Related Documents
- `docs/modules/attestor/proof-chain-specification.md`
- `docs/modules/policy/architecture.md`
- `docs/testing/testing-strategy-models.md`
- `src/Policy/__Libraries/StellaOps.Policy/TrustLattice/K4Lattice.cs`
- `src/Scanner/__Libraries/StellaOps.Scanner.Reachability/Ordering/DeterministicGraphOrderer.cs`
---
## Approval
| Role | Name | Date | Decision |
|------|------|------|----------|
| Architecture Lead | — | 2025-12-24 | Approved |
| Policy Guild Lead | — | 2025-12-24 | Approved |
| Scanner Guild Lead | — | 2025-12-24 | Approved |