# 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.**
