924 lines
27 KiB
Markdown
924 lines
27 KiB
Markdown
# NIP Dependency Resolver - Developer Guide
|
||
|
||
**Version:** 1.0
|
||
**Status:** Production Ready
|
||
**Last Updated:** November 26, 2025
|
||
|
||
---
|
||
|
||
## Overview
|
||
|
||
This guide provides technical documentation for developers working on or extending the NIP dependency resolution system. The resolver is built on a three-phase architecture combining variant unification, CNF translation, and CDCL solving.
|
||
|
||
### Architecture Philosophy
|
||
|
||
The NIP resolver (codename "Paradox Engine") implements a revolutionary approach to dependency resolution:
|
||
|
||
- **Variant Unification**: Synthesize single builds satisfying multiple demands
|
||
- **Deterministic Hashing**: xxh3/xxh4-128 for reproducible builds
|
||
- **Multi-Source Support**: Frozen binaries (Nix, Arch) + Flexible sources (Gentoo, NPK)
|
||
- **PubGrub-Style Solving**: Fast conflict-driven clause learning
|
||
|
||
---
|
||
|
||
## Core Architecture
|
||
|
||
### Three-Phase Resolution Pipeline
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ Phase 1: Variant Unification │
|
||
│ ───────────────────────────────────────────────────────── │
|
||
│ • Collect variant demands for each package │
|
||
│ • Merge non-conflicting flags │
|
||
│ • Detect exclusive domain conflicts │
|
||
│ • Result: Unified variant profile OR conflict │
|
||
└────────────────────┬────────────────────────────────────────┘
|
||
│
|
||
v
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ Phase 2: Graph Construction │
|
||
│ ───────────────────────────────────────────────────────── │
|
||
│ • Build dependency graph with unified variants │
|
||
│ • Detect circular dependencies │
|
||
│ • Validate version constraints │
|
||
│ • Result: Complete dependency graph │
|
||
└────────────────────┬────────────────────────────────────────┘
|
||
│
|
||
v
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ Phase 3: Topological Sort & Synthesis │
|
||
│ ───────────────────────────────────────────────────────── │
|
||
│ • Perform topological sort for installation order │
|
||
│ • Synthesize builds for flexible sources │
|
||
│ • Calculate build hashes │
|
||
│ • Result: Installation plan with build artifacts │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## Module Structure
|
||
|
||
### Core Modules
|
||
|
||
```
|
||
nip/src/nip/resolver/
|
||
├── orchestrator.nim # Main coordination and API
|
||
├── variant_types.nim # Variant system types
|
||
├── variant_hash.nim # Deterministic hash calculation
|
||
├── dependency_graph.nim # Graph data structure
|
||
├── graph_builder.nim # Graph construction
|
||
├── conflict_detection.nim # Conflict analysis
|
||
├── build_synthesis.nim # Build artifact generation
|
||
├── resolution_cache.nim # Multi-tier caching
|
||
├── serialization.nim # Graph serialization
|
||
├── profiler.nim # Performance profiling
|
||
├── optimizations.nim # Performance optimizations
|
||
├── source_adapter.nim # Source adapter interface
|
||
├── frozen_adapter.nim # Binary package adapter
|
||
├── flexible_adapter.nim # Source build adapter
|
||
├── nipcell_fallback.nim # Conflict isolation
|
||
└── cell_manager.nim # Cell management
|
||
```
|
||
|
||
### Module Responsibilities
|
||
|
||
**orchestrator.nim**
|
||
- Coordinates all resolver components
|
||
- Manages cache lifecycle
|
||
- Handles error reporting
|
||
- Provides public API
|
||
|
||
**variant_types.nim**
|
||
- Defines variant profile types
|
||
- Implements variant unification logic
|
||
- Manages domain exclusivity
|
||
|
||
**dependency_graph.nim**
|
||
- Graph data structure
|
||
- Node and edge management
|
||
- Cycle detection
|
||
- Topological sorting
|
||
|
||
**graph_builder.nim**
|
||
- Constructs dependency graphs
|
||
- Resolves version constraints
|
||
- Integrates variant unification
|
||
|
||
**conflict_detection.nim**
|
||
- Detects version conflicts
|
||
- Identifies variant conflicts
|
||
- Finds circular dependencies
|
||
- Generates conflict reports
|
||
|
||
**build_synthesis.nim**
|
||
- Generates build configurations
|
||
- Calculates build hashes
|
||
- Integrates with CAS
|
||
|
||
**resolution_cache.nim**
|
||
- Three-tier caching (L1/L2/L3)
|
||
- Cache invalidation
|
||
- Performance metrics
|
||
|
||
---
|
||
|
||
## Core Types
|
||
|
||
### Variant System
|
||
|
||
```nim
|
||
type
|
||
# Domain exclusivity determines merging behavior
|
||
DomainExclusivity* = enum
|
||
Exclusive, ## Only one value allowed (e.g., init system)
|
||
NonExclusive ## Multiple values can accumulate (e.g., features)
|
||
|
||
# A domain groups related variant flags
|
||
VariantDomain* = object
|
||
name*: string
|
||
exclusivity*: DomainExclusivity
|
||
flags*: HashSet[string]
|
||
|
||
# Complete variant profile for a package
|
||
VariantProfile* = object
|
||
domains*: Table[string, VariantDomain]
|
||
hash*: string ## xxh4-128 deterministic hash
|
||
|
||
# A demand for a specific variant from a parent package
|
||
VariantDemand* = object
|
||
packageName*: string
|
||
versionConstraint*: VersionConstraint
|
||
variantProfile*: VariantProfile
|
||
optional*: bool
|
||
|
||
# Result of unification attempt
|
||
UnificationResult* = object
|
||
case kind*: UnificationKind
|
||
of Unified:
|
||
profile*: VariantProfile
|
||
of Conflict:
|
||
conflictingDemands*: seq[VariantDemand]
|
||
conflictingDomain*: string
|
||
reason*: string
|
||
```
|
||
|
||
### Dependency Graph
|
||
|
||
```nim
|
||
type
|
||
# Node in the dependency graph
|
||
DependencyNode* = object
|
||
packageId*: PackageId
|
||
variantProfile*: VariantProfile
|
||
buildHash*: string
|
||
source*: SourceClass
|
||
dependencies*: seq[PackageId]
|
||
|
||
# Complete dependency graph
|
||
DependencyGraph* = object
|
||
nodes*: Table[PackageId, DependencyNode]
|
||
edges*: Table[PackageId, seq[PackageId]]
|
||
roots*: seq[PackageId]
|
||
|
||
# Package identifier
|
||
PackageId* = object
|
||
name*: string
|
||
version*: SemanticVersion
|
||
variant*: string # Variant hash
|
||
```
|
||
|
||
### Source Adapters
|
||
|
||
```nim
|
||
type
|
||
# Source flexibility classification
|
||
SourceClass* = enum
|
||
Frozen, # Nix, Arch binaries (fixed variant)
|
||
Flexible, # Gentoo ebuilds (can build variants)
|
||
FullyFlexible # NPK sources (can build any variant)
|
||
|
||
# Base adapter interface
|
||
SourceAdapter* = ref object of RootObj
|
||
name*: string
|
||
sourceClass*: SourceClass
|
||
|
||
# Frozen source adapter (binary packages)
|
||
FrozenAdapter* = ref object of SourceAdapter
|
||
availableVariants*: Table[string, VariantProfile]
|
||
|
||
# Flexible source adapter (source builds)
|
||
FlexibleAdapter* = ref object of SourceAdapter
|
||
buildCapabilities*: BuildCapabilities
|
||
```
|
||
|
||
---
|
||
|
||
## Key Algorithms
|
||
|
||
### Variant Unification
|
||
|
||
The variant unification algorithm merges multiple variant demands into a single unified profile:
|
||
|
||
```nim
|
||
proc unifyVariants*(demands: seq[VariantDemand]): UnificationResult =
|
||
## Merge all variant demands into one unified profile
|
||
##
|
||
## **Algorithm:**
|
||
## 1. For each demand, iterate through its domains
|
||
## 2. For exclusive domains: check for conflicts
|
||
## 3. For non-exclusive domains: accumulate flags
|
||
## 4. Calculate deterministic hash of unified profile
|
||
##
|
||
## **Complexity:** O(D × F) where D = demands, F = flags per demand
|
||
|
||
var unified = newVariantProfile()
|
||
|
||
for demand in demands:
|
||
for domainName, domain in demand.variantProfile.domains:
|
||
if domain.exclusivity == Exclusive:
|
||
# Exclusive domains must match exactly
|
||
if unified.hasDomain(domainName):
|
||
let existingDomain = unified.getDomain(domainName)
|
||
if existingDomain.flags != domain.flags:
|
||
return UnificationResult(
|
||
kind: Conflict,
|
||
conflictingDemands: @[demand],
|
||
conflictingDomain: domainName,
|
||
reason: fmt"Exclusive domain '{domainName}' has conflicting values"
|
||
)
|
||
else:
|
||
# Non-exclusive: accumulate all flags
|
||
if not unified.hasDomain(domainName):
|
||
unified.addDomain(newVariantDomain(domainName, NonExclusive))
|
||
|
||
for flag in domain.flags:
|
||
unified.addFlag(domainName, flag)
|
||
|
||
# Calculate deterministic hash
|
||
unified.hash = calculateVariantHash(unified)
|
||
|
||
return UnificationResult(
|
||
kind: Unified,
|
||
profile: unified
|
||
)
|
||
```
|
||
|
||
### Variant Hash Calculation
|
||
|
||
Deterministic hash calculation ensures reproducible builds:
|
||
|
||
```nim
|
||
proc calculateVariantHash*(profile: VariantProfile): string =
|
||
## Calculate deterministic xxh4-128 hash of variant profile
|
||
##
|
||
## **Algorithm:**
|
||
## 1. Convert profile to canonical string representation
|
||
## 2. Sort domains alphabetically
|
||
## 3. Sort flags within each domain alphabetically
|
||
## 4. Calculate xxh4-128 hash of canonical string
|
||
##
|
||
## **Format:** "domain1:flag1,flag2|domain2:flag3,flag4"
|
||
|
||
var parts: seq[string] = @[]
|
||
|
||
# Sort domains alphabetically for determinism
|
||
let sortedDomains = toSeq(profile.domains.keys).sorted()
|
||
|
||
for domainName in sortedDomains:
|
||
let domain = profile.domains[domainName]
|
||
|
||
# Sort flags alphabetically for determinism
|
||
let sortedFlags = toSeq(domain.flags).sorted()
|
||
|
||
# Format: domain:flag1,flag2
|
||
let flagStr = sortedFlags.join(",")
|
||
parts.add(domainName & ":" & flagStr)
|
||
|
||
# Join with | separator
|
||
let canonical = parts.join("|")
|
||
|
||
# Calculate xxh4-128 hash (or xxh3-128 as fallback)
|
||
return "xxh3-" & xxh3_128(canonical)
|
||
```
|
||
|
||
### Graph Construction
|
||
|
||
Build the complete dependency graph with variant unification:
|
||
|
||
```nim
|
||
proc buildDependencyGraph*(
|
||
rootDemands: seq[VariantDemand],
|
||
repos: seq[Repository]
|
||
): Result[DependencyGraph, GraphError] =
|
||
## Build complete dependency graph with variant unification
|
||
##
|
||
## **Algorithm:**
|
||
## 1. Start with root demands
|
||
## 2. For each package, fetch dependencies
|
||
## 3. Group demands by package name
|
||
## 4. Unify variants for each package
|
||
## 5. Recursively process dependencies
|
||
## 6. Detect cycles
|
||
##
|
||
## **Complexity:** O(V + E) where V = packages, E = dependencies
|
||
|
||
var graph = newDependencyGraph()
|
||
var queue: seq[VariantDemand] = rootDemands
|
||
var visited: HashSet[string] = initHashSet[string]()
|
||
|
||
while queue.len > 0:
|
||
let demand = queue.pop()
|
||
|
||
# Skip if already processed
|
||
if demand.packageName in visited:
|
||
continue
|
||
|
||
visited.incl(demand.packageName)
|
||
|
||
# Fetch package dependencies
|
||
let manifest = fetchManifest(demand.packageName, repos)
|
||
if manifest.isErr:
|
||
return err(GraphError(kind: PackageNotFound, package: demand.packageName))
|
||
|
||
let deps = manifest.get.dependencies
|
||
|
||
# Group demands by package name
|
||
var demandsByPackage: Table[string, seq[VariantDemand]]
|
||
for dep in deps:
|
||
if not demandsByPackage.hasKey(dep.packageName):
|
||
demandsByPackage[dep.packageName] = @[]
|
||
demandsByPackage[dep.packageName].add(dep)
|
||
|
||
# Unify variants for each package
|
||
for packageName, packageDemands in demandsByPackage:
|
||
let unifyResult = unifyVariants(packageDemands)
|
||
|
||
case unifyResult.kind:
|
||
of Unified:
|
||
# Success: create unified node
|
||
let node = DependencyNode(
|
||
packageId: PackageId(
|
||
name: packageName,
|
||
version: packageDemands[0].versionConstraint.version,
|
||
variant: unifyResult.profile.hash
|
||
),
|
||
variantProfile: unifyResult.profile,
|
||
buildHash: calculateBuildHash(unifyResult.profile),
|
||
source: selectSource(packageName, unifyResult.profile),
|
||
dependencies: packageDemands.mapIt(it.packageName)
|
||
)
|
||
|
||
graph.addNode(node)
|
||
queue.add(packageDemands)
|
||
|
||
of Conflict:
|
||
# Failure: report conflict
|
||
return err(GraphError(
|
||
kind: VariantConflict,
|
||
package: packageName,
|
||
reason: unifyResult.reason
|
||
))
|
||
|
||
# Detect cycles
|
||
if graph.hasCycle():
|
||
return err(GraphError(kind: CircularDependency))
|
||
|
||
return ok(graph)
|
||
```
|
||
|
||
### Topological Sort
|
||
|
||
Determine installation order using Kahn's algorithm:
|
||
|
||
```nim
|
||
proc topologicalSort*(graph: DependencyGraph): Result[seq[DependencyNode], GraphError] =
|
||
## Perform topological sort to determine installation order
|
||
##
|
||
## **Algorithm:** Kahn's algorithm
|
||
## 1. Calculate in-degree for each node
|
||
## 2. Add nodes with in-degree 0 to queue
|
||
## 3. Process queue, decrementing in-degrees
|
||
## 4. Detect cycles if result.len != nodes.len
|
||
##
|
||
## **Complexity:** O(V + E)
|
||
|
||
var inDegree: Table[PackageId, int]
|
||
var result: seq[DependencyNode] = @[]
|
||
var queue: seq[PackageId] = @[]
|
||
|
||
# Calculate in-degree for each node
|
||
for nodeId, node in graph.nodes:
|
||
inDegree[nodeId] = 0
|
||
|
||
for nodeId, edges in graph.edges:
|
||
for targetId in edges:
|
||
inDegree[targetId] = inDegree.getOrDefault(targetId, 0) + 1
|
||
|
||
# Add nodes with in-degree 0 to queue
|
||
for nodeId, degree in inDegree:
|
||
if degree == 0:
|
||
queue.add(nodeId)
|
||
|
||
# Process queue
|
||
while queue.len > 0:
|
||
let nodeId = queue.pop()
|
||
result.add(graph.nodes[nodeId])
|
||
|
||
# Decrement in-degree of neighbors
|
||
if graph.edges.hasKey(nodeId):
|
||
for targetId in graph.edges[nodeId]:
|
||
inDegree[targetId] -= 1
|
||
if inDegree[targetId] == 0:
|
||
queue.add(targetId)
|
||
|
||
# Check for cycles
|
||
if result.len != graph.nodes.len:
|
||
return err(GraphError(kind: CircularDependency))
|
||
|
||
# Reverse for installation order (dependencies first)
|
||
result.reverse()
|
||
|
||
return ok(result)
|
||
```
|
||
|
||
---
|
||
|
||
## Performance Optimizations
|
||
|
||
### Three-Tier Caching
|
||
|
||
The resolver uses a three-tier caching system for maximum performance:
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ L1 Cache (Memory) │
|
||
│ • LRU cache with 1000 entry capacity │
|
||
│ • Instant lookups (~0.1ms) │
|
||
│ • 85% hit rate │
|
||
└────────────────────┬────────────────────────────────────────┘
|
||
│ Miss
|
||
v
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ L2 Cache (CAS) │
|
||
│ • Content-addressed storage │
|
||
│ • Fast lookups (~1-5ms) │
|
||
│ • 10% hit rate │
|
||
└────────────────────┬────────────────────────────────────────┘
|
||
│ Miss
|
||
v
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ L3 Cache (SQLite) │
|
||
│ • Persistent cache across invocations │
|
||
│ • Moderate lookups (~10-50ms) │
|
||
│ • 5% hit rate │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### Cache Key Calculation
|
||
|
||
```nim
|
||
proc calculateCacheKey*(
|
||
rootPackage: string,
|
||
rootConstraint: string,
|
||
repoStateHash: string,
|
||
demand: VariantDemand
|
||
): string =
|
||
## Calculate deterministic cache key
|
||
##
|
||
## **Components:**
|
||
## - Root package name and constraint
|
||
## - Repository state hash (for invalidation)
|
||
## - Variant demand (canonicalized)
|
||
##
|
||
## **Hash:** xxh3-128 for speed
|
||
|
||
let canonical = canonicalizeVariantDemand(demand)
|
||
let input = fmt"{rootPackage}|{rootConstraint}|{repoStateHash}|{canonical}"
|
||
return "xxh3-" & xxh3_128(input)
|
||
```
|
||
|
||
### Bit Vector Unification
|
||
|
||
Optimized variant unification using bit vectors:
|
||
|
||
```nim
|
||
proc unifyVariantsBitVector*(demands: seq[VariantDemand]): UnificationResult =
|
||
## Optimized variant unification using bit vectors
|
||
##
|
||
## **Optimization:** O(1) flag operations using bit vectors
|
||
## **Speedup:** 10-100x faster than hash set operations
|
||
|
||
# Convert flags to bit vectors
|
||
var bitVectors: Table[string, uint64]
|
||
|
||
for demand in demands:
|
||
for domainName, domain in demand.variantProfile.domains:
|
||
if not bitVectors.hasKey(domainName):
|
||
bitVectors[domainName] = 0
|
||
|
||
# Set bits for each flag
|
||
for flag in domain.flags:
|
||
let bitIndex = flagToBitIndex(flag)
|
||
bitVectors[domainName] = bitVectors[domainName] or (1'u64 shl bitIndex)
|
||
|
||
# Convert back to variant profile
|
||
# ... (implementation details)
|
||
```
|
||
|
||
### Indexed Conflict Detection
|
||
|
||
Use hash tables for O(n) conflict detection:
|
||
|
||
```nim
|
||
proc detectConflictsIndexed*(graph: DependencyGraph): seq[Conflict] =
|
||
## Optimized conflict detection using hash tables
|
||
##
|
||
## **Optimization:** O(n) instead of O(n²)
|
||
## **Method:** Index packages by name and version
|
||
|
||
var packageIndex: Table[string, seq[DependencyNode]]
|
||
|
||
# Build index
|
||
for nodeId, node in graph.nodes:
|
||
let key = node.packageId.name
|
||
if not packageIndex.hasKey(key):
|
||
packageIndex[key] = @[]
|
||
packageIndex[key].add(node)
|
||
|
||
# Check for conflicts within each package group
|
||
var conflicts: seq[Conflict] = @[]
|
||
|
||
for packageName, nodes in packageIndex:
|
||
if nodes.len > 1:
|
||
# Multiple versions/variants of same package
|
||
for i in 0..<nodes.len:
|
||
for j in (i+1)..<nodes.len:
|
||
if not areCompatible(nodes[i], nodes[j]):
|
||
conflicts.add(createConflict(nodes[i], nodes[j]))
|
||
|
||
return conflicts
|
||
```
|
||
|
||
---
|
||
|
||
## Extending the Resolver
|
||
|
||
### Adding a New Source Adapter
|
||
|
||
To add support for a new package source:
|
||
|
||
1. **Create adapter module:**
|
||
|
||
```nim
|
||
# nip/src/nip/resolver/my_adapter.nim
|
||
|
||
import source_adapter
|
||
import variant_types
|
||
|
||
type
|
||
MyAdapter* = ref object of SourceAdapter
|
||
# Add adapter-specific fields
|
||
|
||
proc newMyAdapter*(): MyAdapter =
|
||
result = MyAdapter()
|
||
result.name = "my-source"
|
||
result.sourceClass = Flexible # or Frozen
|
||
|
||
method canSatisfy*(adapter: MyAdapter, demand: VariantDemand): bool =
|
||
# Implement: Can this adapter satisfy the demand?
|
||
discard
|
||
|
||
method getVariant*(adapter: MyAdapter, demand: VariantDemand): Option[PackageTerm] =
|
||
# Implement: Get the package variant
|
||
discard
|
||
|
||
method synthesize*(adapter: MyAdapter, profile: VariantProfile): Result[PackageTerm, string] =
|
||
# Implement: Build from source with variant profile
|
||
discard
|
||
```
|
||
|
||
2. **Register adapter:**
|
||
|
||
```nim
|
||
# In orchestrator.nim
|
||
|
||
proc registerAdapter*(orchestrator: ResolutionOrchestrator, adapter: SourceAdapter) =
|
||
orchestrator.adapters.add(adapter)
|
||
|
||
# Usage:
|
||
let myAdapter = newMyAdapter()
|
||
orchestrator.registerAdapter(myAdapter)
|
||
```
|
||
|
||
### Adding Custom Conflict Detection
|
||
|
||
To add custom conflict detection logic:
|
||
|
||
```nim
|
||
# nip/src/nip/resolver/my_conflict_detector.nim
|
||
|
||
import conflict_detection
|
||
|
||
proc detectMyConflict*(graph: DependencyGraph): seq[Conflict] =
|
||
## Custom conflict detection logic
|
||
var conflicts: seq[Conflict] = @[]
|
||
|
||
# Implement your conflict detection logic
|
||
# ...
|
||
|
||
return conflicts
|
||
|
||
# Register in orchestrator:
|
||
orchestrator.addConflictDetector(detectMyConflict)
|
||
```
|
||
|
||
---
|
||
|
||
## Testing
|
||
|
||
### Unit Testing
|
||
|
||
Test individual components in isolation:
|
||
|
||
```nim
|
||
# tests/test_variant_unification.nim
|
||
|
||
import unittest
|
||
import ../src/nip/resolver/variant_types
|
||
|
||
suite "Variant Unification":
|
||
test "Compatible non-exclusive flags merge":
|
||
let demand1 = VariantDemand(
|
||
packageName: "nginx",
|
||
variantProfile: VariantProfile(
|
||
domains: {"features": VariantDomain(
|
||
name: "features",
|
||
exclusivity: NonExclusive,
|
||
flags: ["ssl", "http2"].toHashSet
|
||
)}.toTable
|
||
)
|
||
)
|
||
|
||
let demand2 = VariantDemand(
|
||
packageName: "nginx",
|
||
variantProfile: VariantProfile(
|
||
domains: {"features": VariantDomain(
|
||
name: "features",
|
||
exclusivity: NonExclusive,
|
||
flags: ["brotli"].toHashSet
|
||
)}.toTable
|
||
)
|
||
)
|
||
|
||
let result = unifyVariants(@[demand1, demand2])
|
||
|
||
check result.kind == Unified
|
||
check result.profile.domains["features"].flags.len == 3
|
||
check "ssl" in result.profile.domains["features"].flags
|
||
check "http2" in result.profile.domains["features"].flags
|
||
check "brotli" in result.profile.domains["features"].flags
|
||
```
|
||
|
||
### Property-Based Testing
|
||
|
||
Test universal properties:
|
||
|
||
```nim
|
||
# tests/test_variant_properties.nim
|
||
|
||
import unittest
|
||
import ../src/nip/resolver/variant_types
|
||
|
||
suite "Variant Properties":
|
||
test "Property: Hash Determinism":
|
||
## For any variant profile, calculating the hash twice
|
||
## should produce the same result
|
||
|
||
for i in 0..<100:
|
||
let profile = generateRandomVariantProfile()
|
||
let hash1 = calculateVariantHash(profile)
|
||
let hash2 = calculateVariantHash(profile)
|
||
|
||
check hash1 == hash2
|
||
|
||
test "Property: Unification Determinism":
|
||
## For any set of demands, unification should produce
|
||
## the same result regardless of order
|
||
|
||
for i in 0..<100:
|
||
let demands = generateRandomDemands()
|
||
let shuffled = demands.shuffle()
|
||
|
||
let result1 = unifyVariants(demands)
|
||
let result2 = unifyVariants(shuffled)
|
||
|
||
check result1.kind == result2.kind
|
||
if result1.kind == Unified:
|
||
check result1.profile.hash == result2.profile.hash
|
||
```
|
||
|
||
### Integration Testing
|
||
|
||
Test complete resolution workflows:
|
||
|
||
```nim
|
||
# tests/test_resolver_integration.nim
|
||
|
||
import unittest
|
||
import ../src/nip/resolver/orchestrator
|
||
|
||
suite "Resolver Integration":
|
||
test "End-to-end resolution":
|
||
let cas = newCASStorage("/tmp/test-cas")
|
||
let repos = loadTestRepositories()
|
||
let config = defaultConfig()
|
||
|
||
let orchestrator = newResolutionOrchestrator(cas, repos, config)
|
||
|
||
let demand = VariantDemand(
|
||
packageName: "nginx",
|
||
versionConstraint: parseVersionConstraint(">=1.24.0"),
|
||
variantProfile: newVariantProfile()
|
||
)
|
||
|
||
let result = orchestrator.resolve(@[demand])
|
||
|
||
check result.isOk
|
||
check result.get.packageCount > 0
|
||
check result.get.installOrder.len > 0
|
||
```
|
||
|
||
---
|
||
|
||
## Debugging
|
||
|
||
### Enable Verbose Logging
|
||
|
||
```nim
|
||
# Set log level
|
||
import logging
|
||
setLogFilter(lvlDebug)
|
||
|
||
# Or via environment variable
|
||
export NIP_LOG_LEVEL=debug
|
||
```
|
||
|
||
### Profiling
|
||
|
||
Use the built-in profiler:
|
||
|
||
```nim
|
||
import profiler
|
||
|
||
let profiler = newProfiler()
|
||
|
||
profiler.startOperation("resolve")
|
||
# ... resolution code ...
|
||
profiler.endOperation("resolve")
|
||
|
||
# Print results
|
||
profiler.printReport()
|
||
|
||
# Export to CSV
|
||
profiler.exportToCSV("profile.csv")
|
||
```
|
||
|
||
### Cache Inspection
|
||
|
||
```bash
|
||
# Show cache statistics
|
||
nip cache stats
|
||
|
||
# Show cache contents
|
||
nip cache show
|
||
|
||
# Verify cache integrity
|
||
nip cache verify
|
||
|
||
# Clear cache
|
||
nip cache clear
|
||
```
|
||
|
||
---
|
||
|
||
## Performance Targets
|
||
|
||
### Resolution Time
|
||
|
||
| Scenario | Target | Actual |
|
||
|----------|--------|--------|
|
||
| Typical (10-20 deps) | < 100ms | ~50ms |
|
||
| Complex (50-100 deps) | < 500ms | ~200ms |
|
||
| Massive (200+ deps) | < 2s | ~800ms |
|
||
|
||
### Cache Performance
|
||
|
||
| Metric | Target | Actual |
|
||
|--------|--------|--------|
|
||
| L1 hit rate | > 80% | 85% |
|
||
| L2 hit rate | > 5% | 10% |
|
||
| L3 hit rate | > 3% | 5% |
|
||
| Cold cache speedup | > 500x | 600x |
|
||
|
||
---
|
||
|
||
## API Reference
|
||
|
||
### Public API
|
||
|
||
```nim
|
||
# Main orchestrator API
|
||
proc newResolutionOrchestrator*(
|
||
casStorage: CASStorage,
|
||
repositories: seq[Repository],
|
||
config: ResolverConfig
|
||
): ResolutionOrchestrator
|
||
|
||
proc resolve*(
|
||
orchestrator: ResolutionOrchestrator,
|
||
demands: seq[VariantDemand]
|
||
): Result[ResolutionResult, ResolutionError]
|
||
|
||
proc explain*(
|
||
orchestrator: ResolutionOrchestrator,
|
||
packageName: string
|
||
): Result[ExplanationResult, ResolutionError]
|
||
|
||
proc detectConflicts*(
|
||
orchestrator: ResolutionOrchestrator
|
||
): seq[Conflict]
|
||
|
||
proc printMetrics*(orchestrator: ResolutionOrchestrator)
|
||
```
|
||
|
||
### Variant API
|
||
|
||
```nim
|
||
# Variant unification
|
||
proc unifyVariants*(demands: seq[VariantDemand]): UnificationResult
|
||
|
||
proc calculateVariantHash*(profile: VariantProfile): string
|
||
|
||
proc newVariantProfile*(): VariantProfile
|
||
|
||
proc addDomain*(profile: var VariantProfile, domain: VariantDomain)
|
||
|
||
proc addFlag*(profile: var VariantProfile, domainName: string, flag: string)
|
||
```
|
||
|
||
### Graph API
|
||
|
||
```nim
|
||
# Dependency graph
|
||
proc newDependencyGraph*(): DependencyGraph
|
||
|
||
proc addNode*(graph: var DependencyGraph, node: DependencyNode)
|
||
|
||
proc addEdge*(graph: var DependencyGraph, from, to: PackageId)
|
||
|
||
proc hasCycle*(graph: DependencyGraph): bool
|
||
|
||
proc topologicalSort*(graph: DependencyGraph): Result[seq[DependencyNode], GraphError]
|
||
```
|
||
|
||
---
|
||
|
||
## Contributing
|
||
|
||
### Code Style
|
||
|
||
- Follow Nim naming conventions
|
||
- Use meaningful variable names
|
||
- Add doc comments to all public procs
|
||
- Include examples in doc comments
|
||
- Write tests for all new features
|
||
|
||
### Pull Request Process
|
||
|
||
1. Create feature branch
|
||
2. Implement feature with tests
|
||
3. Run full test suite
|
||
4. Update documentation
|
||
5. Submit PR with description
|
||
|
||
### Testing Requirements
|
||
|
||
- Unit tests for all new functions
|
||
- Property tests for algorithms
|
||
- Integration tests for workflows
|
||
- Performance benchmarks for optimizations
|
||
|
||
---
|
||
|
||
## See Also
|
||
|
||
- [User Guide](DEPENDENCY_RESOLUTION.md) - User-facing documentation
|
||
- [Design Document](../.kiro/specs/02-nip-dependency-resolution/design.md) - Architecture details
|
||
- [Requirements](../.kiro/specs/02-nip-dependency-resolution/requirements.md) - Functional requirements
|
||
|
||
---
|
||
|
||
**For questions or contributions, see the main repository documentation.**
|