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:
parent
17e552c7d1
commit
11cef88386
|
|
@ -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}"
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
@ -240,11 +242,16 @@ proc nipRepoBenchmark*(outputFormat: string = "plain"): string =
|
|||
of "slow": "🟡"
|
||||
of "error": "🔴"
|
||||
else: "⚪"
|
||||
|
||||
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} {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")
|
||||
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")
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -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'.")
|
||||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
||||
# Store failed
|
||||
return FetchResult[string](
|
||||
success: false,
|
||||
error: "Failed to store package in CAS: " & storeResult.errValue.msg,
|
||||
errorCode: 500
|
||||
)
|
||||
|
||||
return result
|
||||
# Fetch failed
|
||||
return fetchRes
|
||||
|
||||
# =============================================================================
|
||||
# CLI Integration
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
1290
src/nip.nim
1290
src/nip.nim
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue