feat(kernel): implement System Truth Ledger and Causal Trace

- Implemented System Ontology (SPEC-060) and STL (SPEC-061) in Zig HAL
- Created Nim bindings and high-level event emission API
- Integrated STL into kernel boot sequence (SystemBoot, FiberSpawn, CapGrant)
- Implemented Causal Graph Engine (SPEC-062) for lineage tracing
- Verified self-aware causal auditing in boot logs
- Optimized Event structure to 58 bytes for cache efficiency
This commit is contained in:
Markus Maiwald 2026-01-06 03:37:53 +01:00
parent 17e552c7d1
commit 11cef88386
16 changed files with 480 additions and 1326 deletions

View File

@ -417,19 +417,19 @@ proc nipPacmanList*(query: string = ""): Result[string, string] =
else:
adapter.searchPackages(query)
var result = "📦 Pacman Packages"
var listOutput = "📦 Pacman Packages"
if query != "":
result.add(" (matching '" & query & "')")
result.add(":\n\n")
listOutput.add(" (matching '" & query & "')")
listOutput.add(":\n\n")
for pkg in packages:
result.add("" & pkg.name & " " & pkg.version)
listOutput.add("" & pkg.name & " " & pkg.version)
if pkg.description != "":
result.add(" - " & pkg.description)
result.add("\n")
listOutput.add(" - " & pkg.description)
listOutput.add("\n")
result.add("\nTotal: " & $packages.len & " packages")
return Result[string, string](isOk: true, okValue: result)
listOutput.add("\nTotal: " & $packages.len & " packages")
return Result[string, string](isOk: true, okValue: listOutput)
proc nipPacmanInfo*(packageName: string): Result[string, string] =
## NIP command: nip pacman-info <package>
@ -455,21 +455,21 @@ proc nipPacmanDeps*(packageName: string): Result[string, string] =
var visited: seq[string] = @[]
let deps = adapter.getDependencyTree(packageName, visited)
var result = "🌳 Dependency tree for " & packageName & ":\n\n"
var outputStr = "🌳 Dependency tree for " & packageName & ":\n\n"
for i, dep in deps:
let prefix = if i == deps.len - 1: "└── " else: "├── "
result.add(prefix & dep & "\n")
outputStr.add(prefix & dep & "\n")
if deps.len == 0:
result.add("No dependencies found.\n")
outputStr.add("No dependencies found.\n")
else:
result.add("\nTotal dependencies: " & $deps.len)
outputStr.add("\nTotal dependencies: " & $deps.len)
return Result[string, string](isOk: true, okValue: result)
return Result[string, string](isOk: true, okValue: outputStr)
# Grafting adapter methods for coordinator integration
method validatePackage*(adapter: PacmanAdapter, packageName: string): Result[bool, string] =
proc validatePackage*(adapter: PacmanAdapter, packageName: string): Result[bool, string] =
## Validate if a package exists using pacman -Ss (checks repos)
try:
# Use pacman to search for package (checks both local and remote)
@ -491,7 +491,7 @@ proc isPackageInstalled(adapter: PacmanAdapter, packageName: string): bool =
except:
return false
method graftPackage*(adapter: var PacmanAdapter, packageName: string, cache: GraftingCache): GraftResult =
proc graftPackage*(adapter: var PacmanAdapter, packageName: string, cache: GraftingCache): GraftResult =
## Graft a package from Pacman (local or remote)
echo fmt"🌱 Grafting package from Pacman: {packageName}"

View File

@ -12,6 +12,7 @@ import std/[os, strutils, times, json, tables, sequtils, algorithm, strformat]
# TODO: Re-enable when nipcells module is available
# import ../nipcells
import ../grafting, ../database, core
import ../build/[recipe_manager, recipe_parser]
import audit_commands, track_commands, verify_commands
import enhanced_search
@ -170,6 +171,37 @@ proc infoCommand*(packageName: string): CommandResult =
try:
core.showInfo(fmt"Getting information for package: {packageName}")
# Initialize RecipeManager to check Bazaar
let rm = newRecipeManager()
let recipeOpt = rm.loadRecipe(packageName)
if recipeOpt.isSome:
let recipe = recipeOpt.get()
let packageInfo = %*{
"name": recipe.name,
"version": recipe.version,
"description": recipe.description,
"homepage": recipe.metadata.homepage,
"license": recipe.metadata.license,
"stream": "bazaar", # It comes from the Bazaar
"architecture": "multi", # Recipe supports multiple
"installed": false, # We don't check installed status here yet
"source_type": recipe.toolType
}
if globalContext.options.outputFormat == OutputHuman:
echo bold("Package Information (Bazaar): " & highlight(packageName))
echo "=".repeat(30)
echo "Name: " & packageInfo["name"].getStr()
echo "Version: " & highlight(packageInfo["version"].getStr())
echo "Description: " & packageInfo["description"].getStr()
echo "License: " & packageInfo["license"].getStr()
echo "Source Type: " & packageInfo["source_type"].getStr()
return successResult("Package found in Bazaar", packageInfo)
else:
return successResult("Package found in Bazaar", packageInfo)
# Fallback to installed DB check placeholder
# TODO: Implement actual package info retrieval
let packageInfo = %*{
"name": packageName,

View File

@ -4,11 +4,9 @@
## This module provides forward-compatibility hooks for Task 15.2
## and implements immediate diagnostic capabilities
import std/[os, strutils, strformat, tables, sequtils, times, json, asyncdispatch]
import std/[strutils, strformat, sequtils, times, json, asyncdispatch]
import ../security/integrity_monitor
import ../diagnostics/health_monitor
import ../types_fixed
import core
type
DiagnosticSeverity* = enum
@ -156,7 +154,7 @@ proc formatDiagnosticReport*(report: DiagnosticReport, outputFormat: string = "p
else: # plain format
result = "NimPak System Diagnostics\n"
result.add("=" * 30 & "\n\n")
result.add(repeat("=", 30) & "\n\n")
# Overall status
let statusIcon = case report.overall:
@ -166,13 +164,17 @@ proc formatDiagnosticReport*(report: DiagnosticReport, outputFormat: string = "p
of DiagnosticCritical: "🚨"
result.add(fmt"{statusIcon} Overall Status: {report.overall}\n")
result.add(fmt"📅 Generated: {report.timestamp.format(\"yyyy-MM-dd HH:mm:ss\")}\n\n")
let timestampStr = report.timestamp.format("yyyy-MM-dd HH:mm:ss")
result.add(fmt"📅 Generated: {timestampStr}\n\n")
# System information
result.add("System Information:\n")
result.add(fmt" Version: {report.systemInfo[\"nimpak_version\"].getStr()}\n")
result.add(fmt" Platform: {report.systemInfo[\"platform\"].getStr()}\n")
result.add(fmt" Architecture: {report.systemInfo[\"architecture\"].getStr()}\n\n")
let nimpakVersion = report.systemInfo["nimpak_version"].getStr()
result.add(fmt" Version: {nimpakVersion}\n")
let platform = report.systemInfo["platform"].getStr()
result.add(fmt" Platform: {platform}\n")
let architecture = report.systemInfo["architecture"].getStr()
result.add(fmt" Architecture: {architecture}\n\n")
# Diagnostic results
result.add("Diagnostic Results:\n")
@ -232,7 +234,7 @@ proc nipRepoBenchmark*(outputFormat: string = "plain"): string =
return results.pretty()
else:
result = "Repository Benchmark Results\n"
result.add("=" * 35 & "\n\n")
result.add(repeat("=", 35) & "\n\n")
for repo in results["repositories"]:
let status = case repo["status"].getStr():
@ -241,10 +243,15 @@ proc nipRepoBenchmark*(outputFormat: string = "plain"): string =
of "error": "🔴"
else: ""
result.add(fmt"{status} {repo[\"name\"].getStr()}\n")
result.add(fmt" URL: {repo[\"url\"].getStr()}\n")
result.add(fmt" Latency: {repo[\"latency_ms\"].getFloat():.1f}ms\n")
result.add(fmt" Throughput: {repo[\"throughput_mbps\"].getFloat():.1f} MB/s\n\n")
let name = repo["name"].getStr()
let url = repo["url"].getStr()
let latency = repo["latency_ms"].getFloat()
let throughput = repo["throughput_mbps"].getFloat()
result.add(fmt"{status} {name}\n")
result.add(fmt" URL: {url}\n")
result.add(fmt" Latency: {latency:.1f}ms\n")
result.add(fmt" Throughput: {throughput:.1f} MB/s\n\n")
proc nipCacheWarm*(packageName: string): string =
## Pre-pull binary packages into local cache for offline deployment
@ -270,7 +277,7 @@ proc nipMirrorGraph*(outputFormat: string = "plain"): string =
result.add("}\n")
else:
result = "Mirror Network Topology\n"
result.add("=" * 25 & "\n\n")
result.add(repeat("=", 25) & "\n\n")
result.add("Priority Order (High → Low):\n")
result.add(" 1. 🟢 official (100) → community\n")
result.add(" 2. 🔵 community (75) → edge\n")
@ -281,7 +288,7 @@ proc nipMirrorGraph*(outputFormat: string = "plain"): string =
# Forward-Compatibility Hooks for Task 15.2
# =============================================================================
proc nipDoctor*(outputFormat: string = "plain", autoRepair: bool = false): string {.async.} =
proc nipDoctor*(outputFormat: string = "plain", autoRepair: bool = false): Future[string] {.async.} =
## Comprehensive system health check with repair suggestions
try:
# Initialize health monitor
@ -309,7 +316,7 @@ proc nipDoctor*(outputFormat: string = "plain", autoRepair: bool = false): strin
result = fmt"❌ Health check failed: {e.msg}\n"
result.add("💡 Try: nip doctor --force\n")
proc nipRepair*(category: string = "all", dryRun: bool = false): string {.async.} =
proc nipRepair*(category: string = "all", dryRun: bool = false): Future[string] {.async.} =
## System repair command with comprehensive health monitoring integration
result = fmt"🔧 Repair mode: {category}\n"
@ -404,7 +411,7 @@ proc nipInstallWithStream*(packageName: string, repo: string = "",
proc nipTrustExplain*(target: string): string =
## Explain trust policy decisions for repositories or packages
result = fmt"🔍 Trust Analysis: {target}\n"
result.add("=" * (20 + target.len) & "\n\n")
result.add(repeat("=", 20 + target.len) & "\n\n")
# Mock trust analysis
result.add("Trust Score: 0.72 🟡\n\n")

View File

@ -42,6 +42,7 @@ VERIFICATION COMMANDS:
CONFIGURATION COMMANDS:
config show Show current configuration
config validate Validate configuration files
setup <user|system> Setup NIP environment
GLOBAL OPTIONS:
--output <format> Output format: human, json, yaml, kdl
@ -253,6 +254,18 @@ proc showCommandHelp*(command: string) =
echo "nip lock [options] - Generate lockfile for reproducibility"
of "restore":
echo "nip restore <lockfile> [options] - Restore from lockfile"
of "setup":
echo """
nip setup <user|system> - Setup NIP environment
Arguments:
user Configure NIP for the current user (updates shell RC files)
system Configure NIP system-wide (requires root)
Examples:
nip setup user # Add NIP to PATH in ~/.zshrc, ~/.bashrc, etc.
nip setup system # Add NIP to system PATH (not implemented)
"""
else:
echo fmt"Unknown command: {command}"
echo "Use 'nip help' for available commands"

View File

@ -0,0 +1,119 @@
import std/[os, strformat, strutils]
import ../config
import core
proc checkPathConfigured*(): bool =
## Check if NIP binary path is in PATH
let config = loadConfig()
let binPath = config.linksDir / "Executables"
let pathEnv = getEnv("PATH")
# Normalize paths for comparison (remove trailing slashes, resolve symlinks if possible)
# Simple string check for now
return binPath in pathEnv
proc detectShell*(): string =
## Detect the user's shell
let shellPath = getEnv("SHELL")
if shellPath.len > 0:
return shellPath.extractFilename()
return "bash"
proc appendToRcFile(rcFile: string, content: string): bool =
## Append content to an RC file if it's not already there
let home = getHomeDir()
let path = home / rcFile
try:
var currentContent = ""
if fileExists(path):
currentContent = readFile(path)
if content.strip() in currentContent:
return true # Already there
let newContent = if currentContent.len > 0 and not currentContent.endsWith("\n"):
"\n" & content & "\n"
else:
content & "\n"
writeFile(path, currentContent & newContent)
return true
except Exception as e:
echo fmt"❌ Failed to update {rcFile}: {e.msg}"
return false
proc setupUserCommand*(): CommandResult =
## Setup NIP for the current user
let config = loadConfig()
let binPath = config.linksDir / "Executables"
let shell = detectShell()
echo fmt"🌱 Setting up NIP for user (Shell: {shell})..."
echo fmt" Binary Path: {binPath}"
var success = false
case shell:
of "zsh":
let rcContent = fmt"""
# NIP Package Manager
export PATH="{binPath}:$PATH"
"""
if appendToRcFile(".zshrc", rcContent):
echo "✅ Updated .zshrc"
success = true
of "bash":
let rcContent = fmt"""
# NIP Package Manager
export PATH="{binPath}:$PATH"
"""
if appendToRcFile(".bashrc", rcContent):
echo "✅ Updated .bashrc"
success = true
of "fish":
let rcContent = fmt"""
# NIP Package Manager
contains "{binPath}" $fish_user_paths; or set -Ua fish_user_paths "{binPath}"
"""
# Fish is typically in .config/fish/config.fish
# Ensure dir exists
let fishDir = getHomeDir() / ".config" / "fish"
if not dirExists(fishDir):
createDir(fishDir)
if appendToRcFile(".config/fish/config.fish", rcContent):
echo "✅ Updated config.fish"
success = true
else:
return errorResult(fmt"Unsupported shell: {shell}. Please manually add {binPath} to your PATH.")
if success:
echo ""
echo "✨ Setup complete! Please restart your shell or run:"
echo fmt" source ~/.{shell}rc"
return successResult("NIP setup successfully")
else:
return errorResult("Failed to setup NIP")
proc setupSystemCommand*(): CommandResult =
## Setup NIP system-wide
# TODO: Implement system-wide setup (e.g. /etc/profile.d/nip.sh)
return errorResult("System-wide setup not yet implemented")
proc setupCommand*(args: seq[string]): CommandResult =
## Dispatch setup commands
if args.len == 0:
return errorResult("Usage: nip setup <user|system>")
case args[0]:
of "user":
return setupUserCommand()
of "system":
return setupSystemCommand()
else:
return errorResult("Unknown setup target. Use 'user' or 'system'.")

View File

@ -403,3 +403,17 @@ proc saveExampleConfig*(path: string): bool =
except:
echo fmt"❌ Failed to create config at: {path}"
return false
proc getConfigPath*(): string =
## Get the default user configuration file path
let homeDir = getHomeDir()
let xdgConfigHome = getEnv("XDG_CONFIG_HOME", homeDir / ".config")
result = xdgConfigHome / "nip" / "config"
proc initDefaultConfig*() =
## Initialize default configuration if it doesn't exist
let path = getConfigPath()
if not fileExists(path):
if not saveExampleConfig(path):
raise newException(IOError, "Failed to create configuration file")
else:
raise newException(IOError, "Configuration file already exists")

View File

@ -8,10 +8,8 @@
## - Automated repair and recovery systems
## - Performance monitoring and optimization
import std/[os, times, json, tables, sequtils, strutils, strformat, asyncdispatch, algorithm]
import std/[os, times, json, tables, sequtils, strutils, strformat, asyncdispatch]
import ../security/[integrity_monitor, event_logger]
import ../cas
import ../types_fixed
type
HealthCheckCategory* = enum
@ -92,11 +90,15 @@ proc getDefaultHealthMonitorConfig*(): HealthMonitorConfig =
}
)
# Forward declarations
proc getDirSize*(path: string): int64
proc formatHealthReport*(report: HealthReport, format: string = "plain"): string
# =============================================================================
# Package Health Checks
# =============================================================================
proc checkPackageIntegrity*(monitor: HealthMonitor): HealthCheck {.async.} =
proc checkPackageIntegrity*(monitor: HealthMonitor): Future[HealthCheck] {.async.} =
## Check integrity of all installed packages
let startTime = cpuTime()
var check = HealthCheck(
@ -157,7 +159,7 @@ proc checkPackageIntegrity*(monitor: HealthMonitor): HealthCheck {.async.} =
check.duration = cpuTime() - startTime
return check
proc checkPackageConsistency*(monitor: HealthMonitor): HealthCheck {.async.} =
proc checkPackageConsistency*(monitor: HealthMonitor): Future[HealthCheck] {.async.} =
## Check consistency of package installations and dependencies
let startTime = cpuTime()
var check = HealthCheck(
@ -227,7 +229,7 @@ proc checkPackageConsistency*(monitor: HealthMonitor): HealthCheck {.async.} =
# Filesystem Health Checks
# =============================================================================
proc checkFilesystemHealth*(monitor: HealthMonitor): HealthCheck {.async.} =
proc checkFilesystemHealth*(monitor: HealthMonitor): Future[HealthCheck] {.async.} =
## Check filesystem health and disk usage
let startTime = cpuTime()
var check = HealthCheck(
@ -269,8 +271,9 @@ proc checkFilesystemHealth*(monitor: HealthMonitor): HealthCheck {.async.} =
missingDirs.add(dir)
if missingDirs.len > 0:
let missingDirsStr = missingDirs.join(", ")
check.status = StatusCritical
check.message = fmt"Critical directories missing: {missingDirs.join(\", \")}"
check.message = fmt"Critical directories missing: {missingDirsStr}"
check.repairActions = @["nip repair --filesystem", "nip init --restore-structure"]
elif totalSize > 10 * 1024 * 1024 * 1024: # > 10GB
check.status = StatusWarning
@ -293,7 +296,7 @@ proc checkFilesystemHealth*(monitor: HealthMonitor): HealthCheck {.async.} =
# Cache Health Checks
# =============================================================================
proc checkCacheHealth*(monitor: HealthMonitor): HealthCheck {.async.} =
proc checkCacheHealth*(monitor: HealthMonitor): Future[HealthCheck] {.async.} =
## Check cache performance and integrity
let startTime = cpuTime()
var check = HealthCheck(
@ -311,7 +314,8 @@ proc checkCacheHealth*(monitor: HealthMonitor): HealthCheck {.async.} =
try:
# Initialize CAS manager for cache stats
let casManager = newCasManager("~/.nip/cas", "/var/lib/nip/cas")
# Initialize CAS manager for cache stats (stubbed for now if unused)
# let casManager = newCasManager("~/.nip/cas", "/var/lib/nip/cas")
# Simulate cache statistics (would be real in production)
let cacheStats = %*{
@ -338,8 +342,9 @@ proc checkCacheHealth*(monitor: HealthMonitor): HealthCheck {.async.} =
check.message = fmt"High cache fragmentation: {fragmentation:.2f}"
check.repairActions = @["nip cache defrag", "nip cache rebuild"]
else:
let objectCount = cacheStats["object_count"].getInt()
check.status = StatusHealthy
check.message = fmt"Cache healthy: {hitRate:.2f} hit rate, {cacheStats[\"object_count\"].getInt()} objects"
check.message = fmt"Cache healthy: {hitRate:.2f} hit rate, {objectCount} objects"
except Exception as e:
check.status = StatusCritical
@ -354,7 +359,7 @@ proc checkCacheHealth*(monitor: HealthMonitor): HealthCheck {.async.} =
# Repository Health Checks
# =============================================================================
proc checkRepositoryHealth*(monitor: HealthMonitor): HealthCheck {.async.} =
proc checkRepositoryHealth*(monitor: HealthMonitor): Future[HealthCheck] {.async.} =
## Check repository connectivity and trust status
let startTime = cpuTime()
var check = HealthCheck(
@ -441,7 +446,7 @@ proc checkRepositoryHealth*(monitor: HealthMonitor): HealthCheck {.async.} =
# Security Health Checks
# =============================================================================
proc checkSecurityHealth*(monitor: HealthMonitor): HealthCheck {.async.} =
proc checkSecurityHealth*(monitor: HealthMonitor): Future[HealthCheck] {.async.} =
## Check security status including keys, signatures, and trust policies
let startTime = cpuTime()
var check = HealthCheck(
@ -484,8 +489,9 @@ proc checkSecurityHealth*(monitor: HealthMonitor): HealthCheck {.async.} =
check.message = fmt"{expiredKeys} expired keys need rotation"
check.repairActions = @["nip keys rotate", "nip trust update"]
else:
let activeKeys = securityStatus["active_keys"].getInt()
check.status = StatusHealthy
check.message = fmt"Security healthy: {securityStatus[\"active_keys\"].getInt()} active keys, no critical issues"
check.message = fmt"Security healthy: {activeKeys} active keys, no critical issues"
except Exception as e:
check.status = StatusCritical
@ -500,7 +506,7 @@ proc checkSecurityHealth*(monitor: HealthMonitor): HealthCheck {.async.} =
# Performance Monitoring
# =============================================================================
proc checkPerformanceMetrics*(monitor: HealthMonitor): HealthCheck {.async.} =
proc checkPerformanceMetrics*(monitor: HealthMonitor): Future[HealthCheck] {.async.} =
## Monitor system performance metrics
let startTime = cpuTime()
var check = HealthCheck(
@ -559,7 +565,7 @@ proc checkPerformanceMetrics*(monitor: HealthMonitor): HealthCheck {.async.} =
# Health Report Generation
# =============================================================================
proc runAllHealthChecks*(monitor: HealthMonitor): HealthReport {.async.} =
proc runAllHealthChecks*(monitor: HealthMonitor): Future[HealthReport] {.async.} =
## Run all enabled health checks and generate comprehensive report
let startTime = now()
var checks: seq[HealthCheck] = @[]
@ -621,7 +627,7 @@ proc runAllHealthChecks*(monitor: HealthMonitor): HealthReport {.async.} =
# Automated Repair System
# =============================================================================
proc performAutomatedRepair*(monitor: HealthMonitor, report: HealthReport): seq[string] {.async.} =
proc performAutomatedRepair*(monitor: HealthMonitor, report: HealthReport): Future[seq[string]] {.async.} =
## Perform automated repairs based on health report
var repairResults: seq[string] = @[]
@ -698,7 +704,7 @@ proc formatHealthReport*(report: HealthReport, format: string = "plain"): string
else: # plain format
result = "NimPak System Health Report\n"
result.add("=" * 35 & "\n\n")
result.add(repeat("=", 35) & "\n\n")
# Overall status
let statusIcon = case report.overallStatus:
@ -708,7 +714,8 @@ proc formatHealthReport*(report: HealthReport, format: string = "plain"): string
of StatusUnknown: ""
result.add(fmt"{statusIcon} Overall Status: {report.overallStatus}\n")
result.add(fmt"📅 Generated: {report.timestamp.format(\"yyyy-MM-dd HH:mm:ss\")}\n\n")
let timestampStr = report.timestamp.format("yyyy-MM-dd HH:mm:ss")
result.add(fmt"📅 Generated: {timestampStr}\n\n")
# Health checks by category
let categories = [CategoryPackages, CategoryFilesystem, CategoryCache, CategoryRepositories, CategorySecurity, CategoryPerformance]

View File

@ -1048,14 +1048,14 @@ proc storePackageInCas*(format: PackageFormat, data: seq[byte], cas: var CasMana
## Store package format data in content-addressable storage
try:
let storeResult = cas.storeObject(data)
if storeResult.isErr:
if not storeResult.isOk:
return types_fixed.err[CasIntegrationResult, FormatError](FormatError(
code: CasError,
msg: "Failed to store package in CAS: " & storeResult.getError().msg,
code: CasGeneralError,
msg: "Failed to store package in CAS: " & storeResult.errValue.msg,
format: format
))
let casObject = storeResult.get()
let casObject = storeResult.okValue
let result = CasIntegrationResult(
hash: casObject.hash,
size: casObject.size,
@ -1080,14 +1080,14 @@ proc retrievePackageFromCas*(hash: string, cas: var CasManager): types_fixed.Res
## Retrieve package format data from content-addressable storage
try:
let retrieveResult = cas.retrieveObject(hash)
if retrieveResult.isErr:
if not retrieveResult.isOk:
return types_fixed.err[seq[byte], FormatError](FormatError(
code: CasError,
msg: "Failed to retrieve package from CAS: " & retrieveResult.getError().msg,
code: CasGeneralError,
msg: "Failed to retrieve package from CAS: " & retrieveResult.errValue.msg,
format: NpkBinary # Default format for error
))
return types_fixed.ok[seq[byte], FormatError](retrieveResult.get())
return types_fixed.ok[seq[byte], FormatError](retrieveResult.okValue)
except Exception as e:
return types_fixed.err[seq[byte], FormatError](FormatError(
@ -1133,14 +1133,14 @@ proc garbageCollectFormats*(cas: var CasManager, reachableHashes: seq[string] =
let reachableSet = reachableHashes.toHashSet()
let gcResult = cas.garbageCollect(reachableSet)
if gcResult.isErr:
if not gcResult.isOk:
return types_fixed.err[int, FormatError](FormatError(
code: CasError,
msg: "Failed to garbage collect: " & gcResult.getError().msg,
code: CasGeneralError,
msg: "Failed to garbage collect: " & gcResult.errValue.msg,
format: NpkBinary
))
return types_fixed.ok[int, FormatError](gcResult.get())
return types_fixed.ok[int, FormatError](gcResult.okValue)
except Exception as e:
return types_fixed.err[int, FormatError](FormatError(
@ -1234,17 +1234,17 @@ proc convertPackageFormat*(fromPath: string, toPath: string,
# Store in CAS for conversion pipeline
let storeResult = storePackageInCas(fromFormat, sourceBytes, cas)
if storeResult.isErr:
return err[FormatError](storeResult.getError())
if not storeResult.isOk:
return err[FormatError](storeResult.errValue)
let casResult = storeResult.get()
let casResult = storeResult.okValue
# Retrieve and convert (simplified conversion logic)
let retrieveResult = retrievePackageFromCas(casResult.hash, cas)
if retrieveResult.isErr:
return err[FormatError](retrieveResult.getError())
if not retrieveResult.isOk:
return err[FormatError](retrieveResult.errValue)
let convertedData = retrieveResult.get()
let convertedData = retrieveResult.okValue
# Write converted package
let parentDir = toPath.parentDir()
@ -1271,10 +1271,10 @@ proc reconstructPackageFromCas*(hash: string, format: PackageFormat,
## Reconstruct package from CAS storage with format-specific handling
try:
let retrieveResult = retrievePackageFromCas(hash, cas)
if retrieveResult.isErr:
return err[FormatError](retrieveResult.getError())
if not retrieveResult.isOk:
return err[FormatError](retrieveResult.errValue)
let data = retrieveResult.get()
let data = retrieveResult.okValue
# Format-specific reconstruction logic
case format:
@ -1320,7 +1320,7 @@ proc getPackageFormatStats*(cas: var CasManager): types_fixed.Result[JsonNode, F
for objHash in objects:
let retrieveResult = cas.retrieveObject(objHash)
if retrieveResult.isOk:
let data = retrieveResult.get()
let data = retrieveResult.okValue
let size = data.len.int64
# Simple format detection based on content

View File

@ -23,6 +23,7 @@ import std/[os, json, times, strutils, sequtils, tables, options, osproc, strfor
import ./types_fixed
import ./formats
import ./cas except Result, VoidResult, ok, err, ChunkRef
import ./grafting
# KDL parsing will be added when kdl library is available
# For now, we'll use JSON as intermediate format and generate KDL strings
@ -61,12 +62,12 @@ proc createNpkPackage*(fragment: Fragment, sourceDir: string, cas: var CasManage
let storeResult = cas.storeFile(filePath)
if not storeResult.isOk:
return err[NpkPackage, NpkError](NpkError(
code: CasError,
msg: "Failed to store file in CAS: " & storeResult.getError().msg,
code: CasGeneralError,
msg: "Failed to store file in CAS: " & storeResult.errValue.msg,
packageName: fragment.id.name
))
let casObject = storeResult.get()
let casObject = storeResult.okValue
let packageFile = PackageFile(
path: relativePath,
@ -462,7 +463,7 @@ proc extractNpkPackage*(npk: NpkPackage, targetDir: string, cas: var CasManager)
let retrieveResult = cas.retrieveFile(file.hash, targetPath)
if not retrieveResult.isOk:
return err[NpkError](NpkError(
code: CasError,
code: CasGeneralError,
msg: "Failed to retrieve file from CAS: " & retrieveResult.errValue.msg,
packageName: npk.metadata.id.name
))
@ -680,29 +681,75 @@ proc convertGraftToNpk*(graftResult: GraftResult, cas: var CasManager): Result[N
## This includes preserving provenance and audit log information
## Files are stored in CAS for deduplication and integrity verification
# Use the fragment and extractedPath from graftResult to create NPK package
let createResult = createNpkPackage(graftResult.fragment, graftResult.extractedPath, cas)
# Construct Fragment from GraftResult metadata
let pkgId = PackageId(
name: graftResult.metadata.packageName,
version: graftResult.metadata.version,
stream: Custom # Default to Custom for grafts
)
let source = Source(
url: graftResult.metadata.provenance.downloadUrl,
hash: graftResult.metadata.originalHash,
hashAlgorithm: "blake2b", # Default assumption
sourceMethod: Grafted,
timestamp: graftResult.metadata.graftedAt
)
let fragment = Fragment(
id: pkgId,
source: source,
dependencies: @[], # Dependencies not captured in simple GraftResult
buildSystem: Custom,
metadata: PackageMetadata(
description: "Grafted from " & graftResult.metadata.source,
license: "Unknown",
maintainer: "Auto-Graft",
tags: @["grafted"],
runtime: RuntimeProfile(
libc: Glibc, # Assumption
allocator: System,
systemdAware: false,
reproducible: false,
tags: @[]
)
),
acul: AculCompliance(
required: false,
membership: "",
attribution: "Grafted package",
buildLog: graftResult.metadata.buildLog
)
)
let extractedPath = graftResult.metadata.provenance.extractedPath
if extractedPath.len == 0 or not dirExists(extractedPath):
return err[NpkPackage, NpkError](NpkError(
code: PackageNotFound,
msg: "Extracted path not found or empty in graft result",
packageName: pkgId.name
))
# Use the constructed fragment and extractedPath to create NPK package
let createResult = createNpkPackage(fragment, extractedPath, cas)
if not createResult.isOk:
return err[NpkPackage, NpkError](createResult.getError())
return err[NpkPackage, NpkError](createResult.errValue)
var npk = createResult.get()
# Map provenance information from auditLog and originalMetadata
# Embed audit log info into ACUL compliance buildLog for traceability
npk.metadata.acul.buildLog = graftResult.auditLog.sourceOutput
var npk = createResult.okValue
# Map provenance information
# Add provenance information to runtime tags for tracking
let provenanceTag = "grafted:" & $graftResult.auditLog.source & ":" & $graftResult.auditLog.timestamp
let provenanceTag = "grafted:" & graftResult.metadata.source & ":" & $graftResult.metadata.graftedAt
npk.metadata.metadata.runtime.tags.add(provenanceTag)
# Add deduplication status to tags for audit purposes
let deduplicationTag = "dedup:" & graftResult.auditLog.deduplicationStatus.toLowerAscii()
# Add deduplication status to tags for audit purposes (simplified)
let deduplicationTag = "dedup:unknown"
npk.metadata.metadata.runtime.tags.add(deduplicationTag)
# Preserve original archive hash in attribution for full traceability
# Preserve original archive hash in attribution
if npk.metadata.acul.attribution.len > 0:
npk.metadata.acul.attribution.add(" | ")
npk.metadata.acul.attribution.add("Original: " & graftResult.auditLog.blake2bHash)
npk.metadata.acul.attribution.add("Original: " & graftResult.metadata.originalHash)
# Return the constructed NPK package with full provenance
return ok[NpkPackage, NpkError](npk)

View File

@ -513,12 +513,20 @@ proc fetchBinaryPackage*(packageName: string, version: string, url: string,
# Return CAS path
return FetchResult[string](
success: true,
value: storeResult.get().hash,
value: storeResult.okValue.hash,
bytesTransferred: fetchRes.bytesTransferred,
duration: fetchRes.duration
)
return result
# Store failed
return FetchResult[string](
success: false,
error: "Failed to store package in CAS: " & storeResult.errValue.msg,
errorCode: 500
)
# Fetch failed
return fetchRes
# =============================================================================
# CLI Integration

View File

@ -572,7 +572,7 @@ proc createDeltaObject*(engine: SyncEngine, objectHash: string): SyncResult[Delt
errorCode: 404
)
let originalData = objectResult.value
let originalData = objectResult.okValue
let originalSize = int64(originalData.len)
# Compress the data using zstd
@ -630,7 +630,7 @@ proc applyDeltaObject*(engine: SyncEngine, delta: DeltaObject): SyncResult[bool]
if not storeResult.isOk:
return SyncResult[bool](
success: false,
error: fmt"Failed to store object: {storeResult.error.msg}",
error: fmt"Failed to store object: {storeResult.errValue.msg}",
errorCode: 500
)

View File

@ -21,6 +21,8 @@ import ../security/signature_verifier
import ../security/provenance_tracker
import ../remote/manager
import ../types/grafting_types
type
PublishConfig* = object
## Configuration for publishing packages
@ -54,7 +56,7 @@ type
of FromCas:
files*: seq[types_fixed.PackageFile]
of FromGraft:
graftResult*: types_fixed.GraftResult
graftResult*: grafting_types.GraftResult
ArtifactBuilder* = ref object
cas*: CasManager
@ -103,10 +105,10 @@ proc buildFromDirectory*(builder: ArtifactBuilder,
# Store in CAS and get hash
let storeResult = builder.cas.storeObject(dataBytes)
if cas.isErr(storeResult):
if not storeResult.isOk:
return types_fixed.err[NpkPackage, string]("Failed to store file " & file & " in CAS")
let casObj = cas.get(storeResult)
let casObj = storeResult.okValue
let info = getFileInfo(fullPath)
files.add(PackageFile(
@ -359,8 +361,8 @@ proc publish*(builder: ArtifactBuilder,
archiveData.toOpenArrayByte(0, archiveData.len - 1).toSeq()
)
if not cas.isErr(storeResult):
result.casHash = cas.get(storeResult).hash
if storeResult.isOk:
result.casHash = storeResult.okValue.hash
# Step 5: Upload to repository (if configured)
if builder.config.repoId.len > 0:

View File

@ -8,7 +8,7 @@
# nimpak/types.nim
# Core data structures and types for the NimPak system
import std/[times, tables, options, json, hashes]
import std/[hashes]
# Re-export the comprehensive types from types_fixed
include types_fixed

View File

@ -18,10 +18,7 @@ type
variants*: Table[string, VariantRecord] # fingerprint -> record
references*: Table[string, seq[string]] # variant fingerprint -> list of dependent package names (Task 14.2)
# DEPRECATED: Use Option[VariantRecord] instead
VariantQueryResult* {.deprecated: "Use Option[VariantRecord] instead".} = object
found*: bool
record*: VariantRecord
VariantReferenceInfo* = object
## Information about variant references (Task 14.2)
@ -260,19 +257,7 @@ proc queryVariantByFingerprint*(
else:
return none(VariantRecord)
proc queryVariantByFingerprintLegacy*(
db: VariantDatabase,
fingerprint: string
): VariantQueryResult {.deprecated: "Use queryVariantByFingerprint which returns Option[VariantRecord]".} =
## DEPRECATED: Use queryVariantByFingerprint instead
## Look up a variant by its fingerprint (legacy API)
if fingerprint in db.variants:
return VariantQueryResult(
found: true,
record: db.variants[fingerprint]
)
else:
return VariantQueryResult(found: false)
proc queryVariantByPath*(
db: VariantDatabase,
@ -288,21 +273,7 @@ proc queryVariantByPath*(
return none(VariantRecord)
proc queryVariantByPathLegacy*(
db: VariantDatabase,
installPath: string
): VariantQueryResult {.deprecated: "Use queryVariantByPath which returns Option[VariantRecord]".} =
## DEPRECATED: Use queryVariantByPath instead
## Query variant by installation path (legacy API)
for variant in db.variants.values:
if variant.installPath == installPath:
return VariantQueryResult(
found: true,
record: variant
)
return VariantQueryResult(found: false)
proc queryVariantsByPackage*(
db: VariantDatabase,
@ -327,33 +298,7 @@ proc queryVariantsByPackageVersion*(
if variant.packageName == packageName and variant.version == version:
result.add(variant)
proc deleteVariantRecord*(
db: VariantDatabase,
fingerprint: string
): bool {.deprecated: "Use deleteVariantWithReferences to safely handle references".} =
## DEPRECATED: Use deleteVariantWithReferences instead
## Remove a variant record from the database
## WARNING: This does not check for references and may cause dangling references
## Returns true if successful, false if variant not found
# Check for references before deleting
let refs = db.getVariantReferences(fingerprint)
if refs.len > 0:
echo "Warning: Deleting variant with active references: ", refs.join(", ")
echo "Consider using deleteVariantWithReferences instead"
if fingerprint notin db.variants:
return false
db.variants.del(fingerprint)
# Clean up references
if fingerprint in db.references:
db.references.del(fingerprint)
db.saveVariants()
return true
proc updateVariantPath*(
db: VariantDatabase,
@ -420,12 +365,7 @@ proc findVariantByPath*(
# Utility Functions
# #############################################################################
proc `$`*(qr: VariantQueryResult): string {.deprecated.} =
## DEPRECATED: String representation of query result (legacy API)
if qr.found:
result = "Found: " & qr.record.fingerprint
else:
result = "Not found"
proc prettyPrint*(variant: VariantRecord): string =
## Pretty print a variant record

View File

@ -364,7 +364,8 @@ proc hasVariant*(vm: VariantManager, fingerprint: string): bool =
proc deleteVariant*(vm: VariantManager, fingerprint: string): bool =
## Delete a variant from the database
vm.db.deleteVariantRecord(fingerprint)
let (success, _) = vm.db.deleteVariantWithReferences(fingerprint)
return success
proc countVariants*(vm: VariantManager, packageName: string): int =
## Count variants for a package

File diff suppressed because it is too large Load Diff