nip/docs/INTEGRATION_GUIDE.md

15 KiB

Dependency Resolver Integration Guide

Version: 1.0
Last Updated: November 25, 2025
Status: Active Development


Overview

This guide explains how to integrate all components of the NIP dependency resolver into a cohesive system. It covers the complete resolution workflow from package specification to installed artifacts.


Architecture Overview

┌─────────────────────────────────────────────────────────────┐
│                     NIP CLI Interface                        │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                  Resolution Orchestrator                     │
│  - Coordinates all resolver components                      │
│  - Manages cache lifecycle                                  │
│  - Handles error reporting                                  │
└─────────────────────────────────────────────────────────────┘
                              │
        ┌─────────────────────┼─────────────────────┐
        ▼                     ▼                     ▼
┌──────────────┐    ┌──────────────┐    ┌──────────────┐
│   Variant    │    │    Graph     │    │   Solver     │
│  Unification │    │  Builder     │    │   (CDCL)     │
└──────────────┘    └──────────────┘    └──────────────┘
        │                     │                     │
        └─────────────────────┼─────────────────────┘
                              ▼
                    ┌──────────────┐
                    │    Cache     │
                    │  (3-Tier)    │
                    └──────────────┘
                              │
        ┌─────────────────────┼─────────────────────┐
        ▼                     ▼                     ▼
┌──────────────┐    ┌──────────────┐    ┌──────────────┐
│  L1 (Memory) │    │   L2 (CAS)   │    │ L3 (SQLite)  │
└──────────────┘    └──────────────┘    └──────────────┘

Component Integration

1. Resolution Orchestrator

The orchestrator coordinates all resolver components and manages the resolution workflow.

# nip/src/nip/resolver/orchestrator.nim

import ./types
import ./variant
import ./graph_builder
import ./solver
import ./conflict
import ./build_synthesis
import ./resolution_cache
import ../cas/storage

type
  ResolutionOrchestrator* = ref object
    cache: ResolutionCache
    casStorage: CASStorage
    repositories: seq[Repository]
    config: ResolverConfig

  ResolverConfig* = object
    enableCache: bool
    enableParallel: bool
    maxRetries: int
    timeout: Duration

proc newResolutionOrchestrator*(
  casStorage: CASStorage,
  repositories: seq[Repository],
  config: ResolverConfig
): ResolutionOrchestrator =
  result = ResolutionOrchestrator(
    cache: newResolutionCache(casStorage, enabled = config.enableCache),
    casStorage: casStorage,
    repositories: repositories,
    config: config
  )

proc resolve*(
  orchestrator: ResolutionOrchestrator,
  rootPackage: string,
  constraint: string,
  variantDemand: VariantDemand
): Result[DependencyGraph, ResolutionError] =
  ## Main resolution entry point
  
  # 1. Check cache
  let repoHash = calculateGlobalRepoStateHash(orchestrator.repositories)
  orchestrator.cache.updateRepoHash(repoHash)
  
  let cacheKey = CacheKey(
    rootPackage: rootPackage,
    rootConstraint: constraint,
    repoStateHash: repoHash,
    variantDemand: variantDemand
  )
  
  let cached = orchestrator.cache.get(cacheKey)
  if cached.value.isSome:
    return ok(cached.value.get)
  
  # 2. Build dependency graph
  let graphResult = buildDependencyGraph(
    rootPackage,
    constraint,
    variantDemand,
    orchestrator.repositories
  )
  
  if graphResult.isErr:
    return err(graphResult.error)
  
  let graph = graphResult.get
  
  # 3. Solve constraints
  let solverResult = solve(graph)
  if solverResult.isErr:
    # Detect and report conflicts
    let conflicts = detectConflicts(graph)
    return err(ResolutionError(
      kind: ConflictError,
      conflicts: conflicts
    ))
  
  # 4. Synthesize builds
  for node in graph.nodes:
    let buildResult = synthesizeBuild(node, variantDemand)
    if buildResult.isErr:
      return err(buildResult.error)
  
  # 5. Cache result
  orchestrator.cache.put(cacheKey, graph)
  
  return ok(graph)

2. CLI Integration

Connect the orchestrator to the CLI interface.

# nip/src/nip/cli/resolve.nim

import ../resolver/orchestrator
import ../resolver/types
import ../cas/storage

proc resolveCommand*(args: seq[string]): int =
  ## Handle 'nip resolve <package>' command
  
  if args.len < 1:
    echo "Usage: nip resolve <package> [constraint]"
    return 1
  
  let packageName = args[0]
  let constraint = if args.len > 1: args[1] else: "*"
  
  # Load configuration
  let config = loadResolverConfig()
  
  # Initialize components
  let cas = newCASStorage(config.casPath)
  let repos = loadRepositories(config.repoPath)
  
  let orchestrator = newResolutionOrchestrator(cas, repos, config)
  
  # Resolve dependencies
  echo fmt"Resolving {packageName} {constraint}..."
  
  let variantDemand = VariantDemand(
    useFlags: @[],
    libc: "musl",
    allocator: "jemalloc",
    targetArch: "x86_64",
    buildFlags: @[]
  )
  
  let result = orchestrator.resolve(packageName, constraint, variantDemand)
  
  if result.isErr:
    echo "❌ Resolution failed:"
    echo formatError(result.error)
    return 1
  
  let graph = result.get
  
  # Display results
  echo "✅ Resolution successful!"
  echo fmt"Packages: {graph.nodes.len}"
  echo ""
  echo "Installation order:"
  
  let sorted = topologicalSort(graph)
  for i, node in sorted:
    echo fmt"  {i+1}. {node.packageId.name} {node.packageId.version}"
  
  return 0

3. Error Handling Integration

Provide comprehensive error reporting.

# nip/src/nip/resolver/error_reporting.nim

import ./types
import ./conflict

proc formatError*(error: ResolutionError): string =
  ## Format resolution error for user display
  
  case error.kind:
  of ConflictError:
    result = "Dependency conflicts detected:\n\n"
    
    for conflict in error.conflicts:
      result &= formatConflict(conflict)
      result &= "\n"
    
    result &= "\nSuggestions:\n"
    result &= "  • Try relaxing version constraints\n"
    result &= "  • Use NipCell for conflicting packages\n"
    result &= "  • Check for circular dependencies\n"
  
  of PackageNotFoundError:
    result = fmt"Package not found: {error.packageName}\n\n"
    result &= "Suggestions:\n"
    result &= "  • Check package name spelling\n"
    result &= "  • Update repository metadata: nip update\n"
    result &= "  • Search for similar packages: nip search {error.packageName}\n"
  
  of BuildFailureError:
    result = fmt"Build failed for {error.packageName}:\n"
    result &= error.buildLog
    result &= "\n\nSuggestions:\n"
    result &= "  • Check build dependencies\n"
    result &= "  • Review build log for errors\n"
    result &= "  • Try different variant flags\n"
  
  of TimeoutError:
    result = "Resolution timeout exceeded\n\n"
    result &= "Suggestions:\n"
    result &= "  • Increase timeout: nip config set timeout 600\n"
    result &= "  • Check network connectivity\n"
    result &= "  • Simplify dependency constraints\n"

Workflow Examples

Example 1: Simple Package Resolution

# Resolve nginx with default settings
let orchestrator = newResolutionOrchestrator(cas, repos, defaultConfig)

let result = orchestrator.resolve(
  "nginx",
  ">=1.24.0",
  VariantDemand(
    useFlags: @["ssl", "http2"],
    libc: "musl",
    allocator: "jemalloc",
    targetArch: "x86_64",
    buildFlags: @[]
  )
)

if result.isOk:
  let graph = result.get
  echo fmt"Resolved {graph.nodes.len} packages"

Example 2: Complex Resolution with Caching

# First resolution (cold cache)
let start1 = cpuTime()
let result1 = orchestrator.resolve("complex-app", "*", demand)
let time1 = cpuTime() - start1
echo fmt"Cold cache: {time1 * 1000:.2f}ms"

# Second resolution (warm cache)
let start2 = cpuTime()
let result2 = orchestrator.resolve("complex-app", "*", demand)
let time2 = cpuTime() - start2
echo fmt"Warm cache: {time2 * 1000:.2f}ms"

let speedup = time1 / time2
echo fmt"Speedup: {speedup:.2f}x"

Example 3: Conflict Handling

let result = orchestrator.resolve("conflicting-app", "*", demand)

if result.isErr:
  let error = result.error
  
  if error.kind == ConflictError:
    echo "Conflicts detected:"
    
    for conflict in error.conflicts:
      case conflict.kind:
      of VersionConflict:
        echo fmt"  • {conflict.package1} requires {conflict.constraint1}"
        echo fmt"    {conflict.package2} requires {conflict.constraint2}"
      
      of VariantConflict:
        echo fmt"  • Incompatible variants for {conflict.packageName}"
        echo fmt"    {conflict.variant1} vs {conflict.variant2}"
    
    # Suggest NipCell fallback
    echo "\nConsider using NipCell for isolation:"
    echo "  nip cell create app1-env"
    echo "  nip install --cell=app1-env conflicting-app"

Testing Integration

Unit Tests

Test each component independently:

# Test variant unification
suite "Variant Integration":
  test "Unify compatible variants":
    let v1 = VariantDemand(useFlags: @["ssl"])
    let v2 = VariantDemand(useFlags: @["http2"])
    
    let result = unifyVariants(v1, v2)
    check result.isOk
    check result.get.useFlags == @["ssl", "http2"]

Integration Tests

Test complete workflows:

# Test end-to-end resolution
suite "Resolution Integration":
  test "Resolve simple package":
    let orchestrator = setupTestOrchestrator()
    
    let result = orchestrator.resolve("test-pkg", "*", defaultDemand)
    
    check result.isOk
    check result.get.nodes.len > 0

Performance Tests

Validate performance targets:

# Test resolution performance
suite "Performance Integration":
  test "Simple package resolves in <50ms":
    let orchestrator = setupTestOrchestrator()
    
    let start = cpuTime()
    let result = orchestrator.resolve("simple-pkg", "*", defaultDemand)
    let elapsed = cpuTime() - start
    
    check result.isOk
    check elapsed < 0.050  # 50ms

Configuration

Resolver Configuration File

// nip-resolver.kdl
resolver {
  version "1.0"
  
  cache {
    enabled true
    l1_capacity 100
    l2_enabled true
    l3_enabled true
    l3_path "/var/lib/nip/cache.db"
  }
  
  performance {
    parallel_enabled false  // Enable when ready
    max_parallel_jobs 4
    timeout 300  // seconds
    max_retries 3
  }
  
  repositories {
    update_interval "24h"
    verify_signatures true
  }
  
  variants {
    default_libc "musl"
    default_allocator "jemalloc"
    default_arch "x86_64"
  }
}

Deployment Checklist

Pre-Deployment

  • All unit tests passing
  • All integration tests passing
  • Performance benchmarks meet targets
  • Cache invalidation tested
  • Error handling comprehensive
  • Documentation complete

Deployment

  • Deploy resolver components
  • Initialize cache database
  • Configure repositories
  • Set up monitoring
  • Enable profiling (optional)

Post-Deployment

  • Monitor cache hit rates
  • Track resolution times
  • Collect error reports
  • Analyze performance metrics
  • Optimize based on real usage

Monitoring and Observability

Metrics to Track

type
  ResolverMetrics* = object
    totalResolutions*: int
    successfulResolutions*: int
    failedResolutions*: int
    avgResolutionTime*: float
    cacheHitRate*: float
    conflictRate*: float

proc collectMetrics*(orchestrator: ResolutionOrchestrator): ResolverMetrics =
  let cacheMetrics = orchestrator.cache.getMetrics()
  
  return ResolverMetrics(
    totalResolutions: orchestrator.totalResolutions,
    successfulResolutions: orchestrator.successCount,
    failedResolutions: orchestrator.failureCount,
    avgResolutionTime: orchestrator.totalTime / orchestrator.totalResolutions.float,
    cacheHitRate: cacheMetrics.totalHitRate,
    conflictRate: orchestrator.conflictCount.float / orchestrator.totalResolutions.float
  )

Logging

import logging

# Configure logging
let logger = newConsoleLogger(lvlInfo)
addHandler(logger)

# Log resolution events
info("Starting resolution", packageName, constraint)
debug("Cache lookup", cacheKey, cacheResult)
warn("Conflict detected", conflictType, packages)
error("Resolution failed", errorMessage, stackTrace)

Troubleshooting

Common Issues

Issue: Cache not working
Solution: Check cache is enabled in config, verify CAS storage accessible

Issue: Slow resolution
Solution: Enable profiling, identify hot paths, optimize bottlenecks

Issue: Conflicts not detected
Solution: Verify conflict detection enabled, check conflict rules

Issue: Memory usage high
Solution: Reduce L1 cache capacity, enable LRU eviction


Next Steps

  1. Complete Integration: Connect all components in orchestrator
  2. Add CLI Commands: Implement resolve, explain, conflicts commands
  3. Test End-to-End: Run complete workflows with real packages
  4. Optimize Performance: Profile and optimize hot paths
  5. Deploy and Monitor: Deploy to production, track metrics

Document Version: 1.0
Last Updated: November 25, 2025
Status: Active Development