feat: implement Operation Velvet Forge & Evidence Locker
- Ratified 'The Law of Representation' with tiered hashing (XXH3/Ed25519/BLAKE2b). - Implemented RFC 8785 Canonical JSON serialization for deterministic signing. - Deployed 'The Evidence Locker': Registry now enforces mandatory Ed25519 verification on read. - Initialized 'The Cortex': KDL Intent Parser now translates manifests into GraftIntent objects. - Orchestrated 'Velvet Forge' pipeline: Closing the loop between Intent, Synthesis, and Truth. - Resolved xxHash namespace collisions and fixed Nint128 type mismatches. Sovereignty achieved. The machine now listens, remember, and refuses to lie.
This commit is contained in:
parent
1e44dcfaf0
commit
9695382eaf
|
|
@ -15,7 +15,7 @@
|
||||||
import std/[strutils, options, sets, json, sequtils, tables, algorithm]
|
import std/[strutils, options, sets, json, sequtils, tables, algorithm]
|
||||||
import nimpak/kdl_parser
|
import nimpak/kdl_parser
|
||||||
import nip/platform
|
import nip/platform
|
||||||
import nip/xxhash
|
import nip/xxh
|
||||||
|
|
||||||
type
|
type
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
@ -24,15 +24,15 @@ type
|
||||||
|
|
||||||
ManifestFormat* = enum
|
ManifestFormat* = enum
|
||||||
## Supported manifest formats (wire format)
|
## Supported manifest formats (wire format)
|
||||||
FormatKDL = "kdl" ## Human-friendly KDL format
|
FormatKDL = "kdl" ## Human-friendly KDL format
|
||||||
FormatJSON = "json" ## Machine-friendly JSON format
|
FormatJSON = "json" ## Machine-friendly JSON format
|
||||||
FormatAuto = "auto" ## Auto-detect from content
|
FormatAuto = "auto" ## Auto-detect from content
|
||||||
|
|
||||||
FormatType* = enum
|
FormatType* = enum
|
||||||
## Package format types (semantic meaning)
|
## Package format types (semantic meaning)
|
||||||
NPK = "npk" ## Nexus Package Kit (Standard distribution)
|
NPK = "npk" ## Nexus Package Kit (Standard distribution)
|
||||||
NIP = "nip" ## Nexus Installed Package (Local state)
|
NIP = "nip" ## Nexus Installed Package (Local state)
|
||||||
NEXTER = "nexter" ## Nexus Container (Opaque runtime)
|
NEXTER = "nexter" ## Nexus Container (Opaque runtime)
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Validation & Error Handling
|
# Validation & Error Handling
|
||||||
|
|
@ -40,29 +40,29 @@ type
|
||||||
|
|
||||||
ManifestValidationMode* = enum
|
ManifestValidationMode* = enum
|
||||||
## Validation strictness levels
|
## Validation strictness levels
|
||||||
ValidationStrict ## Reject unknown fields, enforce all constraints (DEFAULT)
|
ValidationStrict ## Reject unknown fields, enforce all constraints (DEFAULT)
|
||||||
ValidationLenient ## Warn on unknown fields, allow missing optional fields
|
ValidationLenient ## Warn on unknown fields, allow missing optional fields
|
||||||
ValidationMinimal ## Only validate required fields (unsafe, testing only)
|
ValidationMinimal ## Only validate required fields (unsafe, testing only)
|
||||||
|
|
||||||
ManifestErrorCode* = enum
|
ManifestErrorCode* = enum
|
||||||
## Specific error codes for precise diagnostics
|
## Specific error codes for precise diagnostics
|
||||||
InvalidFormat, ## Syntax error in wire format
|
InvalidFormat, ## Syntax error in wire format
|
||||||
MissingField, ## Required field absent
|
MissingField, ## Required field absent
|
||||||
InvalidValue, ## Field present but value invalid
|
InvalidValue, ## Field present but value invalid
|
||||||
StrictViolation, ## Unknown field detected (contamination)
|
StrictViolation, ## Unknown field detected (contamination)
|
||||||
SemVerViolation, ## Version string not valid semver
|
SemVerViolation, ## Version string not valid semver
|
||||||
SchemaError, ## Structural schema violation
|
SchemaError, ## Structural schema violation
|
||||||
HashMismatch, ## Integrity hash mismatch
|
HashMismatch, ## Integrity hash mismatch
|
||||||
PlatformIncompat, ## Platform/arch constraint violation
|
PlatformIncompat, ## Platform/arch constraint violation
|
||||||
DependencyError ## Dependency specification invalid
|
DependencyError ## Dependency specification invalid
|
||||||
|
|
||||||
ManifestError* = object of CatchableError
|
ManifestError* = object of CatchableError
|
||||||
## Detailed error with context and suggestions
|
## Detailed error with context and suggestions
|
||||||
code*: ManifestErrorCode
|
code*: ManifestErrorCode
|
||||||
field*: string ## Field that caused the error
|
field*: string ## Field that caused the error
|
||||||
line*: int ## Line number (if available)
|
line*: int ## Line number (if available)
|
||||||
context*: string ## Human-readable context
|
context*: string ## Human-readable context
|
||||||
suggestions*: seq[string] ## Actionable suggestions
|
suggestions*: seq[string] ## Actionable suggestions
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Core Manifest Types
|
# Core Manifest Types
|
||||||
|
|
@ -73,7 +73,7 @@ type
|
||||||
# Identity
|
# Identity
|
||||||
format*: FormatType
|
format*: FormatType
|
||||||
name*: string
|
name*: string
|
||||||
version*: SemanticVersion ## Parsed, not string
|
version*: SemanticVersion ## Parsed, not string
|
||||||
description*: Option[string]
|
description*: Option[string]
|
||||||
homepage*: Option[string]
|
homepage*: Option[string]
|
||||||
license*: string
|
license*: string
|
||||||
|
|
@ -89,7 +89,7 @@ type
|
||||||
configureFlags*: seq[string]
|
configureFlags*: seq[string]
|
||||||
|
|
||||||
# Platform constraints (THE PHYSICAL WORLD)
|
# Platform constraints (THE PHYSICAL WORLD)
|
||||||
supportedOS*: seq[string] ## e.g., ["linux", "freebsd"]
|
supportedOS*: seq[string] ## e.g., ["linux", "freebsd"]
|
||||||
supportedArchitectures*: seq[string] ## e.g., ["x86_64", "aarch64"]
|
supportedArchitectures*: seq[string] ## e.g., ["x86_64", "aarch64"]
|
||||||
requiredCapabilities*: seq[string] ## e.g., ["user_namespaces"]
|
requiredCapabilities*: seq[string] ## e.g., ["user_namespaces"]
|
||||||
|
|
||||||
|
|
@ -98,9 +98,9 @@ type
|
||||||
allocator*: Option[string]
|
allocator*: Option[string]
|
||||||
|
|
||||||
# Integrity (cryptographic truth)
|
# Integrity (cryptographic truth)
|
||||||
buildHash*: string ## BLAKE3 hash of build configuration
|
buildHash*: string ## BLAKE3 hash of build configuration
|
||||||
sourceHash*: string ## BLAKE3 hash of source
|
sourceHash*: string ## BLAKE3 hash of source
|
||||||
artifactHash*: string ## BLAKE3 hash of final artifact
|
artifactHash*: string ## BLAKE3 hash of final artifact
|
||||||
|
|
||||||
# Metadata
|
# Metadata
|
||||||
author*: Option[string]
|
author*: Option[string]
|
||||||
|
|
@ -109,8 +109,8 @@ type
|
||||||
maintainers*: seq[string]
|
maintainers*: seq[string]
|
||||||
|
|
||||||
# UTCP support (AI accessibility)
|
# UTCP support (AI accessibility)
|
||||||
utcpEndpoint*: Option[string] ## Remote query endpoint
|
utcpEndpoint*: Option[string] ## Remote query endpoint
|
||||||
utcpVersion*: Option[string] ## UTCP protocol version
|
utcpVersion*: Option[string] ## UTCP protocol version
|
||||||
|
|
||||||
# System Integration
|
# System Integration
|
||||||
files*: seq[FileSpec]
|
files*: seq[FileSpec]
|
||||||
|
|
@ -126,33 +126,33 @@ type
|
||||||
|
|
||||||
DesktopIntegration* = object
|
DesktopIntegration* = object
|
||||||
## Desktop environment integration
|
## Desktop environment integration
|
||||||
displayName*: string ## Human readable name (e.g. "Firefox Web Browser")
|
displayName*: string ## Human readable name (e.g. "Firefox Web Browser")
|
||||||
icon*: Option[string] ## Icon name or path
|
icon*: Option[string] ## Icon name or path
|
||||||
categories*: seq[string] ## Menu categories (e.g. "Network;WebBrowser")
|
categories*: seq[string] ## Menu categories (e.g. "Network;WebBrowser")
|
||||||
keywords*: seq[string] ## Search keywords
|
keywords*: seq[string] ## Search keywords
|
||||||
mimeTypes*: seq[string] ## Supported MIME types
|
mimeTypes*: seq[string] ## Supported MIME types
|
||||||
terminal*: bool ## Run in terminal?
|
terminal*: bool ## Run in terminal?
|
||||||
startupNotify*: bool ## Support startup notification?
|
startupNotify*: bool ## Support startup notification?
|
||||||
startupWMClass*: Option[string] ## For window grouping (StartupWMClass)
|
startupWMClass*: Option[string] ## For window grouping (StartupWMClass)
|
||||||
|
|
||||||
SandboxLevel* = enum
|
SandboxLevel* = enum
|
||||||
SandboxStrict = "strict" ## Maximum isolation (default)
|
SandboxStrict = "strict" ## Maximum isolation (default)
|
||||||
SandboxStandard = "standard" ## Standard desktop app isolation
|
SandboxStandard = "standard" ## Standard desktop app isolation
|
||||||
SandboxRelaxed = "relaxed" ## Minimal isolation (use with caution)
|
SandboxRelaxed = "relaxed" ## Minimal isolation (use with caution)
|
||||||
SandboxNone = "none" ## No isolation (requires user override)
|
SandboxNone = "none" ## No isolation (requires user override)
|
||||||
|
|
||||||
SandboxConfig* = object
|
SandboxConfig* = object
|
||||||
## Sandboxing configuration for NIPs
|
## Sandboxing configuration for NIPs
|
||||||
level*: SandboxLevel
|
level*: SandboxLevel
|
||||||
|
|
||||||
# Linux Specific
|
# Linux Specific
|
||||||
seccompProfile*: Option[string] ## "default", "strict", or custom path
|
seccompProfile*: Option[string] ## "default", "strict", or custom path
|
||||||
capabilities*: seq[string] ## e.g. "CAP_NET_ADMIN" (usually to drop)
|
capabilities*: seq[string] ## e.g. "CAP_NET_ADMIN" (usually to drop)
|
||||||
namespaces*: seq[string] ## e.g. "net", "ipc", "pid"
|
namespaces*: seq[string] ## e.g. "net", "ipc", "pid"
|
||||||
|
|
||||||
# BSD Specific (OpenBSD/DragonflyBSD)
|
# BSD Specific (OpenBSD/DragonflyBSD)
|
||||||
pledge*: Option[string] ## e.g. "stdio rpath wpath inet"
|
pledge*: Option[string] ## e.g. "stdio rpath wpath inet"
|
||||||
unveil*: seq[string] ## e.g. "/tmp:rwc"
|
unveil*: seq[string] ## e.g. "/tmp:rwc"
|
||||||
|
|
||||||
DependencySpec* = object
|
DependencySpec* = object
|
||||||
## Package dependency specification
|
## Package dependency specification
|
||||||
|
|
@ -226,9 +226,9 @@ type
|
||||||
ParserConfig* = object
|
ParserConfig* = object
|
||||||
## Parser configuration
|
## Parser configuration
|
||||||
format*: FormatType
|
format*: FormatType
|
||||||
wireFormat*: ManifestFormat ## KDL or JSON
|
wireFormat*: ManifestFormat ## KDL or JSON
|
||||||
strictMode*: bool
|
strictMode*: bool
|
||||||
allowedFields*: HashSet[string] ## The Whitelist (The Bouncer)
|
allowedFields*: HashSet[string] ## The Whitelist (The Bouncer)
|
||||||
|
|
||||||
ManifestParser* = object
|
ManifestParser* = object
|
||||||
## Parser state and configuration
|
## Parser state and configuration
|
||||||
|
|
@ -248,7 +248,8 @@ const BASE_ALLOWED_FIELDS = [
|
||||||
# Build
|
# Build
|
||||||
"build_system", "build_flags", "configure_flags",
|
"build_system", "build_flags", "configure_flags",
|
||||||
# Platform
|
# Platform
|
||||||
"os", "arch", "supported_os", "supported_architectures", "required_capabilities",
|
"os", "arch", "supported_os", "supported_architectures",
|
||||||
|
"required_capabilities",
|
||||||
# Runtime
|
# Runtime
|
||||||
"libc", "allocator",
|
"libc", "allocator",
|
||||||
# Integrity
|
# Integrity
|
||||||
|
|
@ -331,19 +332,19 @@ proc parseSemanticVersion*(version: string): SemanticVersion =
|
||||||
raise newException(ManifestError,
|
raise newException(ManifestError,
|
||||||
"Invalid semantic version: " & version & " (expected X.Y.Z)")
|
"Invalid semantic version: " & version & " (expected X.Y.Z)")
|
||||||
|
|
||||||
var parts = version.split('-', maxsplit=1)
|
var parts = version.split('-', maxsplit = 1)
|
||||||
var versionPart = parts[0]
|
var versionPart = parts[0]
|
||||||
var prerelease = ""
|
var prerelease = ""
|
||||||
var build = ""
|
var build = ""
|
||||||
|
|
||||||
if parts.len > 1:
|
if parts.len > 1:
|
||||||
var prereleaseAndBuild = parts[1].split('+', maxsplit=1)
|
var prereleaseAndBuild = parts[1].split('+', maxsplit = 1)
|
||||||
prerelease = prereleaseAndBuild[0]
|
prerelease = prereleaseAndBuild[0]
|
||||||
if prereleaseAndBuild.len > 1:
|
if prereleaseAndBuild.len > 1:
|
||||||
build = prereleaseAndBuild[1]
|
build = prereleaseAndBuild[1]
|
||||||
else:
|
else:
|
||||||
# Check for build metadata without prerelease
|
# Check for build metadata without prerelease
|
||||||
parts = versionPart.split('+', maxsplit=1)
|
parts = versionPart.split('+', maxsplit = 1)
|
||||||
versionPart = parts[0]
|
versionPart = parts[0]
|
||||||
if parts.len > 1:
|
if parts.len > 1:
|
||||||
build = parts[1]
|
build = parts[1]
|
||||||
|
|
@ -387,9 +388,9 @@ proc compareVersions*(a, b: SemanticVersion): int =
|
||||||
|
|
||||||
# Prerelease comparison
|
# Prerelease comparison
|
||||||
if a.prerelease.len == 0 and b.prerelease.len > 0:
|
if a.prerelease.len == 0 and b.prerelease.len > 0:
|
||||||
return 1 # Release > prerelease
|
return 1 # Release > prerelease
|
||||||
if a.prerelease.len > 0 and b.prerelease.len == 0:
|
if a.prerelease.len > 0 and b.prerelease.len == 0:
|
||||||
return -1 # Prerelease < release
|
return -1 # Prerelease < release
|
||||||
if a.prerelease != b.prerelease:
|
if a.prerelease != b.prerelease:
|
||||||
return cmp(a.prerelease, b.prerelease)
|
return cmp(a.prerelease, b.prerelease)
|
||||||
|
|
||||||
|
|
@ -442,7 +443,8 @@ proc parseVersionConstraint*(constraint: string): VersionConstraint =
|
||||||
let version = parseSemanticVersion(versionStr)
|
let version = parseSemanticVersion(versionStr)
|
||||||
result = VersionConstraint(operator: operator, version: version)
|
result = VersionConstraint(operator: operator, version: version)
|
||||||
|
|
||||||
proc satisfiesConstraint*(version: SemanticVersion, constraint: VersionConstraint): bool =
|
proc satisfiesConstraint*(version: SemanticVersion,
|
||||||
|
constraint: VersionConstraint): bool =
|
||||||
## Check if a version satisfies a constraint
|
## Check if a version satisfies a constraint
|
||||||
case constraint.operator:
|
case constraint.operator:
|
||||||
of OpAny:
|
of OpAny:
|
||||||
|
|
@ -488,20 +490,20 @@ proc createStrictStructRule*(allowed: HashSet[string]): ValidationRule =
|
||||||
name: "strict_whitelist",
|
name: "strict_whitelist",
|
||||||
description: "Ensures only authorized fields are present",
|
description: "Ensures only authorized fields are present",
|
||||||
validate: proc(data: JsonNode, errors: var seq[ManifestError]): bool =
|
validate: proc(data: JsonNode, errors: var seq[ManifestError]): bool =
|
||||||
var valid = true
|
var valid = true
|
||||||
for key in data.keys:
|
for key in data.keys:
|
||||||
if key notin allowed:
|
if key notin allowed:
|
||||||
valid = false
|
valid = false
|
||||||
errors.add(ManifestError(
|
errors.add(ManifestError(
|
||||||
code: StrictViolation,
|
code: StrictViolation,
|
||||||
field: key,
|
field: key,
|
||||||
context: "Unauthorized field found: '" & key & "'",
|
context: "Unauthorized field found: '" & key & "'",
|
||||||
suggestions: @[
|
suggestions: @[
|
||||||
"Remove the field",
|
"Remove the field",
|
||||||
"Check spelling against spec",
|
"Check spelling against spec",
|
||||||
"This field may be format-specific" ]
|
"This field may be format-specific"]
|
||||||
))
|
))
|
||||||
return valid
|
return valid
|
||||||
)
|
)
|
||||||
|
|
||||||
proc createSemVerRule*(fieldName: string): ValidationRule =
|
proc createSemVerRule*(fieldName: string): ValidationRule =
|
||||||
|
|
@ -510,22 +512,22 @@ proc createSemVerRule*(fieldName: string): ValidationRule =
|
||||||
name: "semver_" & fieldName,
|
name: "semver_" & fieldName,
|
||||||
description: "Field '" & fieldName & "' must be valid SemVer (X.Y.Z)",
|
description: "Field '" & fieldName & "' must be valid SemVer (X.Y.Z)",
|
||||||
validate: proc(data: JsonNode, errors: var seq[ManifestError]): bool =
|
validate: proc(data: JsonNode, errors: var seq[ManifestError]): bool =
|
||||||
if fieldName notin data: return true # Required check handles missing
|
if fieldName notin data: return true # Required check handles missing
|
||||||
|
|
||||||
let val = data[fieldName].getStr()
|
let val = data[fieldName].getStr()
|
||||||
if not isValidSemVer(val):
|
if not isValidSemVer(val):
|
||||||
errors.add(ManifestError(
|
errors.add(ManifestError(
|
||||||
code: SemVerViolation,
|
code: SemVerViolation,
|
||||||
field: fieldName,
|
field: fieldName,
|
||||||
context: "'" & val & "' is not a valid SemVer",
|
context: "'" & val & "' is not a valid SemVer",
|
||||||
suggestions: @[
|
suggestions: @[
|
||||||
"Use format X.Y.Z (e.g., 1.0.0)",
|
"Use format X.Y.Z (e.g., 1.0.0)",
|
||||||
"Prerelease: 1.0.0-alpha",
|
"Prerelease: 1.0.0-alpha",
|
||||||
"Build metadata: 1.0.0+20130313144700"
|
"Build metadata: 1.0.0+20130313144700"
|
||||||
]
|
]
|
||||||
))
|
))
|
||||||
return false
|
return false
|
||||||
return true
|
return true
|
||||||
)
|
)
|
||||||
|
|
||||||
proc createPlatformConstraintRule*(): ValidationRule =
|
proc createPlatformConstraintRule*(): ValidationRule =
|
||||||
|
|
@ -534,77 +536,77 @@ proc createPlatformConstraintRule*(): ValidationRule =
|
||||||
name: "platform_constraints",
|
name: "platform_constraints",
|
||||||
description: "Validates OS and Architecture targets",
|
description: "Validates OS and Architecture targets",
|
||||||
validate: proc(data: JsonNode, errors: var seq[ManifestError]): bool =
|
validate: proc(data: JsonNode, errors: var seq[ManifestError]): bool =
|
||||||
var valid = true
|
var valid = true
|
||||||
|
|
||||||
# Validate OS field
|
# Validate OS field
|
||||||
if "os" in data or "supported_os" in data:
|
if "os" in data or "supported_os" in data:
|
||||||
let osField = if "os" in data: "os" else: "supported_os"
|
let osField = if "os" in data: "os" else: "supported_os"
|
||||||
let osNode = data[osField]
|
let osNode = data[osField]
|
||||||
|
|
||||||
if osNode.kind != JArray:
|
if osNode.kind != JArray:
|
||||||
errors.add(ManifestError(
|
errors.add(ManifestError(
|
||||||
code: InvalidValue,
|
code: InvalidValue,
|
||||||
field: osField,
|
field: osField,
|
||||||
context: "OS field must be an array",
|
context: "OS field must be an array",
|
||||||
suggestions: @["Use array format: [\"linux\", \"freebsd\"]"]
|
suggestions: @["Use array format: [\"linux\", \"freebsd\"]"]
|
||||||
))
|
))
|
||||||
valid = false
|
valid = false
|
||||||
elif osNode.len == 0:
|
elif osNode.len == 0:
|
||||||
errors.add(ManifestError(
|
errors.add(ManifestError(
|
||||||
code: InvalidValue,
|
code: InvalidValue,
|
||||||
field: osField,
|
field: osField,
|
||||||
context: "OS array cannot be empty",
|
context: "OS array cannot be empty",
|
||||||
suggestions: @["Specify at least one OS"]
|
suggestions: @["Specify at least one OS"]
|
||||||
))
|
))
|
||||||
valid = false
|
valid = false
|
||||||
else:
|
else:
|
||||||
# Validate each OS value
|
# Validate each OS value
|
||||||
for osVal in osNode:
|
for osVal in osNode:
|
||||||
let os = osVal.getStr()
|
let os = osVal.getStr()
|
||||||
if os notin VALID_OS:
|
if os notin VALID_OS:
|
||||||
errors.add(ManifestError(
|
errors.add(ManifestError(
|
||||||
code: PlatformIncompat,
|
code: PlatformIncompat,
|
||||||
field: osField,
|
field: osField,
|
||||||
context: "Invalid OS: " & os,
|
context: "Invalid OS: " & os,
|
||||||
suggestions: @["Valid OS: " & $VALID_OS]
|
suggestions: @["Valid OS: " & $VALID_OS]
|
||||||
))
|
))
|
||||||
valid = false
|
valid = false
|
||||||
|
|
||||||
# Validate Architecture field
|
# Validate Architecture field
|
||||||
if "arch" in data or "supported_architectures" in data:
|
if "arch" in data or "supported_architectures" in data:
|
||||||
let archField = if "arch" in data: "arch" else: "supported_architectures"
|
let archField = if "arch" in data: "arch" else: "supported_architectures"
|
||||||
let archNode = data[archField]
|
let archNode = data[archField]
|
||||||
|
|
||||||
if archNode.kind != JArray:
|
if archNode.kind != JArray:
|
||||||
errors.add(ManifestError(
|
errors.add(ManifestError(
|
||||||
code: InvalidValue,
|
code: InvalidValue,
|
||||||
field: archField,
|
field: archField,
|
||||||
context: "Architecture field must be an array",
|
context: "Architecture field must be an array",
|
||||||
suggestions: @["Use array format: [\"x86_64\", \"aarch64\"]"]
|
suggestions: @["Use array format: [\"x86_64\", \"aarch64\"]"]
|
||||||
))
|
))
|
||||||
valid = false
|
valid = false
|
||||||
elif archNode.len == 0:
|
elif archNode.len == 0:
|
||||||
errors.add(ManifestError(
|
errors.add(ManifestError(
|
||||||
code: InvalidValue,
|
code: InvalidValue,
|
||||||
field: archField,
|
field: archField,
|
||||||
context: "Architecture array cannot be empty",
|
context: "Architecture array cannot be empty",
|
||||||
suggestions: @["Specify at least one architecture"]
|
suggestions: @["Specify at least one architecture"]
|
||||||
))
|
))
|
||||||
valid = false
|
valid = false
|
||||||
else:
|
else:
|
||||||
# Validate each architecture value
|
# Validate each architecture value
|
||||||
for archVal in archNode:
|
for archVal in archNode:
|
||||||
let arch = archVal.getStr()
|
let arch = archVal.getStr()
|
||||||
if arch notin VALID_ARCHITECTURES:
|
if arch notin VALID_ARCHITECTURES:
|
||||||
errors.add(ManifestError(
|
errors.add(ManifestError(
|
||||||
code: PlatformIncompat,
|
code: PlatformIncompat,
|
||||||
field: archField,
|
field: archField,
|
||||||
context: "Invalid architecture: " & arch,
|
context: "Invalid architecture: " & arch,
|
||||||
suggestions: @["Valid architectures: " & $VALID_ARCHITECTURES]
|
suggestions: @["Valid architectures: " & $VALID_ARCHITECTURES]
|
||||||
))
|
))
|
||||||
valid = false
|
valid = false
|
||||||
|
|
||||||
return valid
|
return valid
|
||||||
)
|
)
|
||||||
|
|
||||||
proc createRequiredFieldsRule*(fields: seq[string]): ValidationRule =
|
proc createRequiredFieldsRule*(fields: seq[string]): ValidationRule =
|
||||||
|
|
@ -613,17 +615,17 @@ proc createRequiredFieldsRule*(fields: seq[string]): ValidationRule =
|
||||||
name: "required_fields",
|
name: "required_fields",
|
||||||
description: "Ensures all required fields are present",
|
description: "Ensures all required fields are present",
|
||||||
validate: proc(data: JsonNode, errors: var seq[ManifestError]): bool =
|
validate: proc(data: JsonNode, errors: var seq[ManifestError]): bool =
|
||||||
var valid = true
|
var valid = true
|
||||||
for field in fields:
|
for field in fields:
|
||||||
if field notin data:
|
if field notin data:
|
||||||
errors.add(ManifestError(
|
errors.add(ManifestError(
|
||||||
code: MissingField,
|
code: MissingField,
|
||||||
field: field,
|
field: field,
|
||||||
context: "Required field '" & field & "' is missing",
|
context: "Required field '" & field & "' is missing",
|
||||||
suggestions: @["Add the field to the manifest"]
|
suggestions: @["Add the field to the manifest"]
|
||||||
))
|
))
|
||||||
valid = false
|
valid = false
|
||||||
return valid
|
return valid
|
||||||
)
|
)
|
||||||
|
|
||||||
proc createDependencyRule*(): ValidationRule =
|
proc createDependencyRule*(): ValidationRule =
|
||||||
|
|
@ -632,61 +634,62 @@ proc createDependencyRule*(): ValidationRule =
|
||||||
name: "dependencies",
|
name: "dependencies",
|
||||||
description: "Validates dependency specifications",
|
description: "Validates dependency specifications",
|
||||||
validate: proc(data: JsonNode, errors: var seq[ManifestError]): bool =
|
validate: proc(data: JsonNode, errors: var seq[ManifestError]): bool =
|
||||||
var valid = true
|
var valid = true
|
||||||
|
|
||||||
for depField in ["dependencies", "build_dependencies", "optional_dependencies"]:
|
for depField in ["dependencies", "build_dependencies",
|
||||||
if depField notin data: continue
|
"optional_dependencies"]:
|
||||||
|
if depField notin data: continue
|
||||||
|
|
||||||
let deps = data[depField]
|
let deps = data[depField]
|
||||||
if deps.kind != JArray:
|
if deps.kind != JArray:
|
||||||
|
errors.add(ManifestError(
|
||||||
|
code: SchemaError,
|
||||||
|
field: depField,
|
||||||
|
context: "Dependencies must be an array",
|
||||||
|
suggestions: @["Use array format"]
|
||||||
|
))
|
||||||
|
valid = false
|
||||||
|
continue
|
||||||
|
|
||||||
|
for dep in deps:
|
||||||
|
if dep.kind != JObject:
|
||||||
errors.add(ManifestError(
|
errors.add(ManifestError(
|
||||||
code: SchemaError,
|
code: SchemaError,
|
||||||
field: depField,
|
field: depField,
|
||||||
context: "Dependencies must be an array",
|
context: "Each dependency must be an object",
|
||||||
suggestions: @["Use array format"]
|
suggestions: @["Use object format: {\"name\": \"pkg\", \"version\": \">=1.0.0\"}"]
|
||||||
))
|
))
|
||||||
valid = false
|
valid = false
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for dep in deps:
|
# Check required dependency fields
|
||||||
if dep.kind != JObject:
|
if "name" notin dep:
|
||||||
errors.add(ManifestError(
|
errors.add(ManifestError(
|
||||||
code: SchemaError,
|
code: MissingField,
|
||||||
field: depField,
|
field: depField & ".name",
|
||||||
context: "Each dependency must be an object",
|
context: "Dependency missing 'name' field",
|
||||||
suggestions: @["Use object format: {\"name\": \"pkg\", \"version\": \">=1.0.0\"}"]
|
suggestions: @["Add name field"]
|
||||||
))
|
))
|
||||||
valid = false
|
valid = false
|
||||||
continue
|
|
||||||
|
|
||||||
# Check required dependency fields
|
# Validate version constraint if present
|
||||||
if "name" notin dep:
|
if "version" in dep:
|
||||||
|
let versionStr = dep["version"].getStr()
|
||||||
|
try:
|
||||||
|
discard parseVersionConstraint(versionStr)
|
||||||
|
except ManifestError as e:
|
||||||
errors.add(ManifestError(
|
errors.add(ManifestError(
|
||||||
code: MissingField,
|
code: DependencyError,
|
||||||
field: depField & ".name",
|
field: depField & ".version",
|
||||||
context: "Dependency missing 'name' field",
|
context: "Invalid version constraint: " & versionStr,
|
||||||
suggestions: @["Add name field"]
|
suggestions: @[
|
||||||
|
"Use valid constraint: >=1.0.0, ~1.2.0, ^2.0.0",
|
||||||
|
e.msg
|
||||||
|
]
|
||||||
))
|
))
|
||||||
valid = false
|
valid = false
|
||||||
|
|
||||||
# Validate version constraint if present
|
return valid
|
||||||
if "version" in dep:
|
|
||||||
let versionStr = dep["version"].getStr()
|
|
||||||
try:
|
|
||||||
discard parseVersionConstraint(versionStr)
|
|
||||||
except ManifestError as e:
|
|
||||||
errors.add(ManifestError(
|
|
||||||
code: DependencyError,
|
|
||||||
field: depField & ".version",
|
|
||||||
context: "Invalid version constraint: " & versionStr,
|
|
||||||
suggestions: @[
|
|
||||||
"Use valid constraint: >=1.0.0, ~1.2.0, ^2.0.0",
|
|
||||||
e.msg
|
|
||||||
]
|
|
||||||
))
|
|
||||||
valid = false
|
|
||||||
|
|
||||||
return valid
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
@ -769,7 +772,8 @@ proc checkPlatformCompatibility*(manifest: PackageManifest,
|
||||||
# JSON Parsing (Machine-Friendly)
|
# JSON Parsing (Machine-Friendly)
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
proc parseManifestFromJSON*(content: string, parser: var ManifestParser): PackageManifest =
|
proc parseManifestFromJSON*(content: string,
|
||||||
|
parser: var ManifestParser): PackageManifest =
|
||||||
## Parse package manifest from JSON format
|
## Parse package manifest from JSON format
|
||||||
## This is the machine-friendly format for automated systems
|
## This is the machine-friendly format for automated systems
|
||||||
|
|
||||||
|
|
@ -865,7 +869,8 @@ proc parseManifestFromJSON*(content: string, parser: var ManifestParser): Packag
|
||||||
optional: false
|
optional: false
|
||||||
)
|
)
|
||||||
if dep.hasKey("version"):
|
if dep.hasKey("version"):
|
||||||
depSpec.versionConstraint = parseVersionConstraint(dep["version"].getStr())
|
depSpec.versionConstraint = parseVersionConstraint(dep[
|
||||||
|
"version"].getStr())
|
||||||
if dep.hasKey("optional"):
|
if dep.hasKey("optional"):
|
||||||
depSpec.optional = dep["optional"].getBool()
|
depSpec.optional = dep["optional"].getBool()
|
||||||
if dep.hasKey("features"):
|
if dep.hasKey("features"):
|
||||||
|
|
@ -880,7 +885,8 @@ proc parseManifestFromJSON*(content: string, parser: var ManifestParser): Packag
|
||||||
optional: false
|
optional: false
|
||||||
)
|
)
|
||||||
if dep.hasKey("version"):
|
if dep.hasKey("version"):
|
||||||
depSpec.versionConstraint = parseVersionConstraint(dep["version"].getStr())
|
depSpec.versionConstraint = parseVersionConstraint(dep[
|
||||||
|
"version"].getStr())
|
||||||
manifest.buildDependencies.add(depSpec)
|
manifest.buildDependencies.add(depSpec)
|
||||||
|
|
||||||
if jsonNode.hasKey("optional_dependencies"):
|
if jsonNode.hasKey("optional_dependencies"):
|
||||||
|
|
@ -890,7 +896,8 @@ proc parseManifestFromJSON*(content: string, parser: var ManifestParser): Packag
|
||||||
optional: true
|
optional: true
|
||||||
)
|
)
|
||||||
if dep.hasKey("version"):
|
if dep.hasKey("version"):
|
||||||
depSpec.versionConstraint = parseVersionConstraint(dep["version"].getStr())
|
depSpec.versionConstraint = parseVersionConstraint(dep[
|
||||||
|
"version"].getStr())
|
||||||
manifest.optionalDependencies.add(depSpec)
|
manifest.optionalDependencies.add(depSpec)
|
||||||
|
|
||||||
# Build configuration
|
# Build configuration
|
||||||
|
|
@ -1016,7 +1023,8 @@ proc parseManifestFromJSON*(content: string, parser: var ManifestParser): Packag
|
||||||
# KDL Parsing (Human-Friendly) - NATIVE IMPLEMENTATION
|
# KDL Parsing (Human-Friendly) - NATIVE IMPLEMENTATION
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
proc parseManifestFromKDL*(content: string, parser: var ManifestParser): PackageManifest =
|
proc parseManifestFromKDL*(content: string,
|
||||||
|
parser: var ManifestParser): PackageManifest =
|
||||||
## Parse package manifest from KDL format using NATIVE KDL structures
|
## Parse package manifest from KDL format using NATIVE KDL structures
|
||||||
## No JSON conversion - direct KDL parsing for maximum efficiency
|
## No JSON conversion - direct KDL parsing for maximum efficiency
|
||||||
|
|
||||||
|
|
@ -1333,7 +1341,8 @@ proc parseManifestFromKDL*(content: string, parser: var ManifestParser): Package
|
||||||
manifest.desktop = some(dt)
|
manifest.desktop = some(dt)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if parser.config.strictMode and child.name notin parser.config.allowedFields:
|
if parser.config.strictMode and child.name notin
|
||||||
|
parser.config.allowedFields:
|
||||||
raise newException(ManifestError, "Unknown field: " & child.name)
|
raise newException(ManifestError, "Unknown field: " & child.name)
|
||||||
else:
|
else:
|
||||||
parser.warnings.add("Unknown field: " & child.name)
|
parser.warnings.add("Unknown field: " & child.name)
|
||||||
|
|
@ -1460,7 +1469,8 @@ proc serializeManifestToJSON*(manifest: PackageManifest): string =
|
||||||
for dep in manifest.dependencies:
|
for dep in manifest.dependencies:
|
||||||
var depObj = %* {"name": dep.name}
|
var depObj = %* {"name": dep.name}
|
||||||
if dep.versionConstraint.operator != OpAny:
|
if dep.versionConstraint.operator != OpAny:
|
||||||
depObj["version"] = %($dep.versionConstraint.operator & $dep.versionConstraint.version)
|
depObj["version"] = %($dep.versionConstraint.operator &
|
||||||
|
$dep.versionConstraint.version)
|
||||||
if dep.optional:
|
if dep.optional:
|
||||||
depObj["optional"] = %true
|
depObj["optional"] = %true
|
||||||
if dep.features.len > 0:
|
if dep.features.len > 0:
|
||||||
|
|
@ -1617,7 +1627,8 @@ proc serializeManifestToKDL*(manifest: PackageManifest): string =
|
||||||
for dep in manifest.dependencies:
|
for dep in manifest.dependencies:
|
||||||
result.add(" \"" & dep.name & "\"")
|
result.add(" \"" & dep.name & "\"")
|
||||||
if dep.versionConstraint.operator != OpAny:
|
if dep.versionConstraint.operator != OpAny:
|
||||||
result.add(" version=\"" & $dep.versionConstraint.operator & $dep.versionConstraint.version & "\"")
|
result.add(" version=\"" & $dep.versionConstraint.operator &
|
||||||
|
$dep.versionConstraint.version & "\"")
|
||||||
if dep.optional:
|
if dep.optional:
|
||||||
result.add(" optional=true")
|
result.add(" optional=true")
|
||||||
if dep.features.len > 0:
|
if dep.features.len > 0:
|
||||||
|
|
@ -1631,7 +1642,8 @@ proc serializeManifestToKDL*(manifest: PackageManifest): string =
|
||||||
for dep in manifest.buildDependencies:
|
for dep in manifest.buildDependencies:
|
||||||
result.add(" \"" & dep.name & "\"")
|
result.add(" \"" & dep.name & "\"")
|
||||||
if dep.versionConstraint.operator != OpAny:
|
if dep.versionConstraint.operator != OpAny:
|
||||||
result.add(" version=\"" & $dep.versionConstraint.operator & $dep.versionConstraint.version & "\"")
|
result.add(" version=\"" & $dep.versionConstraint.operator &
|
||||||
|
$dep.versionConstraint.version & "\"")
|
||||||
result.add("\n")
|
result.add("\n")
|
||||||
result.add(" }\n")
|
result.add(" }\n")
|
||||||
|
|
||||||
|
|
@ -1641,7 +1653,8 @@ proc serializeManifestToKDL*(manifest: PackageManifest): string =
|
||||||
for dep in manifest.optionalDependencies:
|
for dep in manifest.optionalDependencies:
|
||||||
result.add(" \"" & dep.name & "\"")
|
result.add(" \"" & dep.name & "\"")
|
||||||
if dep.versionConstraint.operator != OpAny:
|
if dep.versionConstraint.operator != OpAny:
|
||||||
result.add(" version=\"" & $dep.versionConstraint.operator & $dep.versionConstraint.version & "\"")
|
result.add(" version=\"" & $dep.versionConstraint.operator &
|
||||||
|
$dep.versionConstraint.version & "\"")
|
||||||
if dep.features.len > 0:
|
if dep.features.len > 0:
|
||||||
result.add(" features=\"" & dep.features.join(",") & "\"")
|
result.add(" features=\"" & dep.features.join(",") & "\"")
|
||||||
result.add("\n")
|
result.add("\n")
|
||||||
|
|
@ -1696,13 +1709,15 @@ proc serializeManifestToKDL*(manifest: PackageManifest): string =
|
||||||
if manifest.files.len > 0:
|
if manifest.files.len > 0:
|
||||||
result.add("\n files {\n")
|
result.add("\n files {\n")
|
||||||
for file in manifest.files:
|
for file in manifest.files:
|
||||||
result.add(" file \"" & file.path & "\" hash=\"" & file.hash & "\" size=" & $file.size & " permissions=\"" & file.permissions & "\"\n")
|
result.add(" file \"" & file.path & "\" hash=\"" & file.hash &
|
||||||
|
"\" size=" & $file.size & " permissions=\"" & file.permissions & "\"\n")
|
||||||
result.add(" }\n")
|
result.add(" }\n")
|
||||||
|
|
||||||
if manifest.users.len > 0:
|
if manifest.users.len > 0:
|
||||||
result.add("\n users {\n")
|
result.add("\n users {\n")
|
||||||
for user in manifest.users:
|
for user in manifest.users:
|
||||||
result.add(" \"" & user.name & "\" group=\"" & user.group & "\" shell=\"" & user.shell & "\" home=\"" & user.home & "\"")
|
result.add(" \"" & user.name & "\" group=\"" & user.group &
|
||||||
|
"\" shell=\"" & user.shell & "\" home=\"" & user.home & "\"")
|
||||||
if user.uid.isSome:
|
if user.uid.isSome:
|
||||||
result.add(" uid=" & $user.uid.get())
|
result.add(" uid=" & $user.uid.get())
|
||||||
result.add("\n")
|
result.add("\n")
|
||||||
|
|
@ -1720,7 +1735,8 @@ proc serializeManifestToKDL*(manifest: PackageManifest): string =
|
||||||
if manifest.services.len > 0:
|
if manifest.services.len > 0:
|
||||||
result.add("\n services {\n")
|
result.add("\n services {\n")
|
||||||
for service in manifest.services:
|
for service in manifest.services:
|
||||||
result.add(" \"" & service.name & "\" enabled=" & $service.enabled & " content=" & service.content.escape() & "\n")
|
result.add(" \"" & service.name & "\" enabled=" & $service.enabled &
|
||||||
|
" content=" & service.content.escape() & "\n")
|
||||||
result.add(" }\n")
|
result.add(" }\n")
|
||||||
|
|
||||||
# Security / Sandbox
|
# Security / Sandbox
|
||||||
|
|
@ -1729,7 +1745,8 @@ proc serializeManifestToKDL*(manifest: PackageManifest): string =
|
||||||
result.add("\n sandbox level=\"" & $sb.level & "\" {\n")
|
result.add("\n sandbox level=\"" & $sb.level & "\" {\n")
|
||||||
|
|
||||||
# Linux
|
# Linux
|
||||||
if sb.seccompProfile.isSome or sb.capabilities.len > 0 or sb.namespaces.len > 0:
|
if sb.seccompProfile.isSome or sb.capabilities.len > 0 or
|
||||||
|
sb.namespaces.len > 0:
|
||||||
result.add(" linux")
|
result.add(" linux")
|
||||||
if sb.seccompProfile.isSome:
|
if sb.seccompProfile.isSome:
|
||||||
result.add(" seccomp=\"" & sb.seccompProfile.get() & "\"")
|
result.add(" seccomp=\"" & sb.seccompProfile.get() & "\"")
|
||||||
|
|
@ -1767,7 +1784,8 @@ proc serializeManifestToKDL*(manifest: PackageManifest): string =
|
||||||
# Desktop Integration
|
# Desktop Integration
|
||||||
if manifest.desktop.isSome:
|
if manifest.desktop.isSome:
|
||||||
let dt = manifest.desktop.get()
|
let dt = manifest.desktop.get()
|
||||||
result.add("\n desktop display_name=\"" & dt.displayName & "\" terminal=" & $dt.terminal & " startup_notify=" & $dt.startupNotify)
|
result.add("\n desktop display_name=\"" & dt.displayName & "\" terminal=" &
|
||||||
|
$dt.terminal & " startup_notify=" & $dt.startupNotify)
|
||||||
if dt.icon.isSome:
|
if dt.icon.isSome:
|
||||||
result.add(" icon=\"" & dt.icon.get() & "\"")
|
result.add(" icon=\"" & dt.icon.get() & "\"")
|
||||||
if dt.startupWMClass.isSome:
|
if dt.startupWMClass.isSome:
|
||||||
|
|
@ -1842,7 +1860,8 @@ proc calculateManifestHash*(manifest: PackageManifest): string =
|
||||||
for dep in manifest.dependencies:
|
for dep in manifest.dependencies:
|
||||||
var depStr = "dep:" & dep.name
|
var depStr = "dep:" & dep.name
|
||||||
if dep.versionConstraint.operator != OpAny:
|
if dep.versionConstraint.operator != OpAny:
|
||||||
depStr.add(":" & $dep.versionConstraint.operator & $dep.versionConstraint.version)
|
depStr.add(":" & $dep.versionConstraint.operator &
|
||||||
|
$dep.versionConstraint.version)
|
||||||
if dep.optional:
|
if dep.optional:
|
||||||
depStr.add(":optional")
|
depStr.add(":optional")
|
||||||
if dep.features.len > 0:
|
if dep.features.len > 0:
|
||||||
|
|
@ -1855,7 +1874,8 @@ proc calculateManifestHash*(manifest: PackageManifest): string =
|
||||||
for dep in manifest.buildDependencies:
|
for dep in manifest.buildDependencies:
|
||||||
var depStr = "builddep:" & dep.name
|
var depStr = "builddep:" & dep.name
|
||||||
if dep.versionConstraint.operator != OpAny:
|
if dep.versionConstraint.operator != OpAny:
|
||||||
depStr.add(":" & $dep.versionConstraint.operator & $dep.versionConstraint.version)
|
depStr.add(":" & $dep.versionConstraint.operator &
|
||||||
|
$dep.versionConstraint.version)
|
||||||
buildDepStrings.add(depStr)
|
buildDepStrings.add(depStr)
|
||||||
components.add(buildDepStrings.sorted().join("|"))
|
components.add(buildDepStrings.sorted().join("|"))
|
||||||
|
|
||||||
|
|
@ -1864,7 +1884,8 @@ proc calculateManifestHash*(manifest: PackageManifest): string =
|
||||||
for dep in manifest.optionalDependencies:
|
for dep in manifest.optionalDependencies:
|
||||||
var depStr = "optdep:" & dep.name
|
var depStr = "optdep:" & dep.name
|
||||||
if dep.versionConstraint.operator != OpAny:
|
if dep.versionConstraint.operator != OpAny:
|
||||||
depStr.add(":" & $dep.versionConstraint.operator & $dep.versionConstraint.version)
|
depStr.add(":" & $dep.versionConstraint.operator &
|
||||||
|
$dep.versionConstraint.version)
|
||||||
if dep.features.len > 0:
|
if dep.features.len > 0:
|
||||||
depStr.add(":features=" & dep.features.sorted().join(","))
|
depStr.add(":features=" & dep.features.sorted().join(","))
|
||||||
optDepStrings.add(depStr)
|
optDepStrings.add(depStr)
|
||||||
|
|
@ -1907,7 +1928,8 @@ proc calculateManifestHash*(manifest: PackageManifest): string =
|
||||||
# 13. System Integration (sorted for determinism)
|
# 13. System Integration (sorted for determinism)
|
||||||
var fileStrings: seq[string] = @[]
|
var fileStrings: seq[string] = @[]
|
||||||
for file in manifest.files:
|
for file in manifest.files:
|
||||||
fileStrings.add("file:" & file.path & ":" & file.hash & ":" & $file.size & ":" & file.permissions)
|
fileStrings.add("file:" & file.path & ":" & file.hash & ":" & $file.size &
|
||||||
|
":" & file.permissions)
|
||||||
components.add(fileStrings.sorted().join("|"))
|
components.add(fileStrings.sorted().join("|"))
|
||||||
|
|
||||||
var userStrings: seq[string] = @[]
|
var userStrings: seq[string] = @[]
|
||||||
|
|
@ -1926,7 +1948,8 @@ proc calculateManifestHash*(manifest: PackageManifest): string =
|
||||||
|
|
||||||
var serviceStrings: seq[string] = @[]
|
var serviceStrings: seq[string] = @[]
|
||||||
for service in manifest.services:
|
for service in manifest.services:
|
||||||
serviceStrings.add("service:" & service.name & ":" & $service.enabled & ":" & service.content)
|
serviceStrings.add("service:" & service.name & ":" & $service.enabled &
|
||||||
|
":" & service.content)
|
||||||
components.add(serviceStrings.sorted().join("|"))
|
components.add(serviceStrings.sorted().join("|"))
|
||||||
|
|
||||||
# 14. Security / Sandbox
|
# 14. Security / Sandbox
|
||||||
|
|
@ -1951,7 +1974,8 @@ proc calculateManifestHash*(manifest: PackageManifest): string =
|
||||||
# 15. Desktop Integration
|
# 15. Desktop Integration
|
||||||
if manifest.desktop.isSome:
|
if manifest.desktop.isSome:
|
||||||
let dt = manifest.desktop.get()
|
let dt = manifest.desktop.get()
|
||||||
var dtStr = "desktop:" & dt.displayName & ":" & $dt.terminal & ":" & $dt.startupNotify
|
var dtStr = "desktop:" & dt.displayName & ":" & $dt.terminal & ":" &
|
||||||
|
$dt.startupNotify
|
||||||
|
|
||||||
if dt.icon.isSome:
|
if dt.icon.isSome:
|
||||||
dtStr.add(":icon=" & dt.icon.get())
|
dtStr.add(":icon=" & dt.icon.get())
|
||||||
|
|
@ -1972,7 +1996,8 @@ proc calculateManifestHash*(manifest: PackageManifest): string =
|
||||||
|
|
||||||
return $hash
|
return $hash
|
||||||
|
|
||||||
proc verifyManifestHash*(manifest: PackageManifest, expectedHash: string): bool =
|
proc verifyManifestHash*(manifest: PackageManifest,
|
||||||
|
expectedHash: string): bool =
|
||||||
## Verify that a manifest matches the expected hash
|
## Verify that a manifest matches the expected hash
|
||||||
## Returns true if hash matches, false otherwise
|
## Returns true if hash matches, false otherwise
|
||||||
let calculatedHash = calculateManifestHash(manifest)
|
let calculatedHash = calculateManifestHash(manifest)
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
|
|
||||||
import std/[os, strutils, times, options, sequtils, osproc, logging]
|
import std/[os, strutils, times, options, sequtils, osproc, logging]
|
||||||
import nip/cas
|
import nip/cas
|
||||||
import nip/xxhash
|
import nip/xxh
|
||||||
import nip/nexter_manifest
|
import nip/nexter_manifest
|
||||||
|
|
||||||
type
|
type
|
||||||
|
|
@ -97,7 +97,8 @@ proc parseNEXTER*(path: string): NEXTERContainer =
|
||||||
try:
|
try:
|
||||||
# Extract archive using tar with zstd decompression
|
# Extract archive using tar with zstd decompression
|
||||||
# Using --auto-compress lets tar detect compression automatically
|
# Using --auto-compress lets tar detect compression automatically
|
||||||
let extractCmd = "tar --auto-compress -xf " & quoteShell(path) & " -C " & quoteShell(tempDir)
|
let extractCmd = "tar --auto-compress -xf " & quoteShell(path) & " -C " &
|
||||||
|
quoteShell(tempDir)
|
||||||
let exitCode = execCmd(extractCmd)
|
let exitCode = execCmd(extractCmd)
|
||||||
|
|
||||||
if exitCode != 0:
|
if exitCode != 0:
|
||||||
|
|
@ -208,7 +209,8 @@ proc createNEXTER*(manifest: NEXTERManifest, environment: string, chunks: seq[Ch
|
||||||
writeFile(tempDir / "signature.sig", signature)
|
writeFile(tempDir / "signature.sig", signature)
|
||||||
|
|
||||||
# Create tar.zst archive
|
# Create tar.zst archive
|
||||||
let createCmd = "tar --auto-compress -cf " & quoteShell(outputPath) & " -C " & quoteShell(tempDir) & " ."
|
let createCmd = "tar --auto-compress -cf " & quoteShell(outputPath) &
|
||||||
|
" -C " & quoteShell(tempDir) & " ."
|
||||||
let exitCode = execCmd(createCmd)
|
let exitCode = execCmd(createCmd)
|
||||||
|
|
||||||
if exitCode != 0:
|
if exitCode != 0:
|
||||||
|
|
@ -247,7 +249,7 @@ proc extractChunksToCAS*(container: NEXTERContainer, casRoot: string): seq[strin
|
||||||
for chunk in container.chunks:
|
for chunk in container.chunks:
|
||||||
try:
|
try:
|
||||||
# Decompress chunk
|
# Decompress chunk
|
||||||
let decompressed = chunk.data # TODO: Implement zstd decompression
|
let decompressed = chunk.data # TODO: Implement zstd decompression
|
||||||
|
|
||||||
# Verify hash
|
# Verify hash
|
||||||
let calculatedHash = "xxh3-" & $calculateXXH3(decompressed)
|
let calculatedHash = "xxh3-" & $calculateXXH3(decompressed)
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
|
|
||||||
import std/[os, strutils, times, json, options, sequtils]
|
import std/[os, strutils, times, json, options, sequtils]
|
||||||
import nip/cas
|
import nip/cas
|
||||||
import nip/xxhash
|
import nip/xxh
|
||||||
import nip/npk_manifest
|
import nip/npk_manifest
|
||||||
import nip/manifest_parser
|
import nip/manifest_parser
|
||||||
|
|
||||||
|
|
@ -97,7 +97,8 @@ proc parseNPK*(path: string): NPKPackage =
|
||||||
try:
|
try:
|
||||||
# Extract archive using tar with zstd decompression
|
# Extract archive using tar with zstd decompression
|
||||||
# Using --auto-compress lets tar detect compression automatically
|
# Using --auto-compress lets tar detect compression automatically
|
||||||
let extractCmd = "tar --auto-compress -xf " & quoteShell(path) & " -C " & quoteShell(tempDir)
|
let extractCmd = "tar --auto-compress -xf " & quoteShell(path) & " -C " &
|
||||||
|
quoteShell(tempDir)
|
||||||
let extractResult = execShellCmd(extractCmd)
|
let extractResult = execShellCmd(extractCmd)
|
||||||
|
|
||||||
if extractResult != 0:
|
if extractResult != 0:
|
||||||
|
|
@ -133,7 +134,7 @@ proc parseNPK*(path: string): NPKPackage =
|
||||||
hash: chunkHash,
|
hash: chunkHash,
|
||||||
data: chunkData,
|
data: chunkData,
|
||||||
size: chunkData.len.int64,
|
size: chunkData.len.int64,
|
||||||
chunkType: Binary # Will be determined from manifest
|
chunkType: Binary # Will be determined from manifest
|
||||||
))
|
))
|
||||||
|
|
||||||
# Load signature
|
# Load signature
|
||||||
|
|
@ -343,11 +344,13 @@ proc packageSize*(pkg: NPKPackage): int64 =
|
||||||
|
|
||||||
proc `$`*(pkg: NPKPackage): string =
|
proc `$`*(pkg: NPKPackage): string =
|
||||||
## Convert NPK package to human-readable string
|
## Convert NPK package to human-readable string
|
||||||
result = "NPK Package: " & pkg.manifest.name & " v" & manifest_parser.`$`(pkg.manifest.version) & "\n"
|
result = "NPK Package: " & pkg.manifest.name & " v" & manifest_parser.`$`(
|
||||||
|
pkg.manifest.version) & "\n"
|
||||||
result.add("Archive: " & pkg.archivePath & "\n")
|
result.add("Archive: " & pkg.archivePath & "\n")
|
||||||
result.add("Chunks: " & $pkg.chunks.len & "\n")
|
result.add("Chunks: " & $pkg.chunks.len & "\n")
|
||||||
result.add("Total Size: " & $(packageSize(pkg) div 1024) & " KB\n")
|
result.add("Total Size: " & $(packageSize(pkg) div 1024) & " KB\n")
|
||||||
result.add("Signature: " & (if pkg.signature.len > 0: "Present" else: "Missing") & "\n")
|
result.add("Signature: " & (if pkg.signature.len >
|
||||||
|
0: "Present" else: "Missing") & "\n")
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Error Formatting
|
# Error Formatting
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import std/[strutils]
|
||||||
# We'll use a conditional import to handle the case where xxhash isn't installed yet
|
# We'll use a conditional import to handle the case where xxhash isn't installed yet
|
||||||
when defined(useXXHash):
|
when defined(useXXHash):
|
||||||
import xxhash
|
import xxhash
|
||||||
|
import nint128 # Required for UInt128 toHex
|
||||||
else:
|
else:
|
||||||
# Fallback implementation using a simple hash for development
|
# Fallback implementation using a simple hash for development
|
||||||
# This will be replaced with actual xxhash once the library is installed
|
# This will be replaced with actual xxhash once the library is installed
|
||||||
|
|
@ -41,7 +42,8 @@ when defined(useXXHash):
|
||||||
proc calculateXXH3*(data: seq[byte]): XXH3Hash =
|
proc calculateXXH3*(data: seq[byte]): XXH3Hash =
|
||||||
## Calculate xxh3-128 hash of a byte sequence
|
## Calculate xxh3-128 hash of a byte sequence
|
||||||
## Returns hash in format: "xxh3-<hex-digest>"
|
## Returns hash in format: "xxh3-<hex-digest>"
|
||||||
let hash128 = XXH3_128bits(cast[ptr UncheckedArray[byte]](unsafeAddr data[0]), data.len)
|
let hash128 = XXH3_128bits(cast[ptr UncheckedArray[byte]](unsafeAddr data[
|
||||||
|
0]), csize_t(data.len))
|
||||||
let hexDigest = hash128.toHex().toLowerAscii()
|
let hexDigest = hash128.toHex().toLowerAscii()
|
||||||
result = XXH3Hash("xxh3-" & hexDigest)
|
result = XXH3Hash("xxh3-" & hexDigest)
|
||||||
|
|
||||||
|
|
@ -109,7 +111,7 @@ when isMainModule:
|
||||||
echo "✗ Hash verification failed"
|
echo "✗ Hash verification failed"
|
||||||
|
|
||||||
# Test byte sequence hashing
|
# Test byte sequence hashing
|
||||||
let testBytes = @[byte(72), byte(101), byte(108), byte(108), byte(111)] # "Hello"
|
let testBytes = @[byte(72), byte(101), byte(108), byte(108), byte(111)] # "Hello"
|
||||||
let bytesHash = calculateXXH3(testBytes)
|
let bytesHash = calculateXXH3(testBytes)
|
||||||
echo "Bytes hash: ", $bytesHash
|
echo "Bytes hash: ", $bytesHash
|
||||||
|
|
||||||
Loading…
Reference in New Issue