Private/Test-KadenPackagePayload.ps1
|
function Test-KadenPackagePayload { <# .SYNOPSIS Check that the installed payload has its required directories, manifest, and source digests. #> [CmdletBinding()] param( [string]$PayloadRoot = (Get-KadenPayloadRoot) ) $requiredDirs = @( (Join-Path $PayloadRoot 'scripts') # NuGet default excludes omit folders that start with '.'; pack as github/cursor. (Join-Path $PayloadRoot 'github') (Join-Path $PayloadRoot 'cursor') (Join-Path (Join-Path $PayloadRoot 'docs') 'kaden') (Join-Path $PayloadRoot 'schemas') (Join-Path (Join-Path $PayloadRoot 'templates') 'ai-overlays') ) foreach ($dir in $requiredDirs) { if (-not (Test-Path -LiteralPath $dir)) { return [pscustomobject]@{ Valid = $false Message = "KDN-PAYLOAD-INCOMPLETE: Installed Kaden payload is incomplete (missing '$dir'). Repair the module installation and retry." } } } $manifestPath = Join-Path $PayloadRoot 'kaden-managed-files.json' if (-not (Test-Path -LiteralPath $manifestPath)) { return [pscustomobject]@{ Valid = $false Message = 'KDN-PAYLOAD-INCOMPLETE: Installed Kaden payload is incomplete (missing managed-file manifest). Repair the module installation and retry.' } } try { $manifest = Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json } catch { return [pscustomobject]@{ Valid = $false Message = 'KDN-PAYLOAD-INCOMPLETE: Installed Kaden payload has an invalid managed-file manifest. Repair the module installation and retry.' } } if (-not $manifest.files -or @($manifest.files).Count -lt 1) { return [pscustomobject]@{ Valid = $false Message = 'KDN-PAYLOAD-INCOMPLETE: Installed Kaden payload has an empty managed-file manifest. Repair the module installation and retry.' } } if ([string]$manifest.schemaVersion -ne '1') { return [pscustomobject]@{ Valid = $false Message = 'KDN-PAYLOAD-INCOMPLETE: Installed Kaden payload has an unsupported managed-file manifest schema. Repair the module installation and retry.' } } $sourcePaths = @{} $destinationPaths = @{} foreach ($entry in @($manifest.files)) { $sourceRelativePath = [string]$entry.source $destinationRelativePath = [string]$entry.destination $digestValue = [string]$entry.digest try { $sourceKey = ConvertTo-KadenNormalizedManifestPath -RelativePath $sourceRelativePath $destinationKey = ConvertTo-KadenNormalizedManifestPath -RelativePath $destinationRelativePath } catch { return [pscustomobject]@{ Valid = $false Message = "KDN-PAYLOAD-INCOMPLETE: $($_.Exception.Message)" } } if ($sourcePaths.ContainsKey($sourceKey) -or $destinationPaths.ContainsKey($destinationKey)) { return [pscustomobject]@{ Valid = $false Message = 'KDN-PAYLOAD-INCOMPLETE: Installed Kaden payload has duplicate managed-file paths. Repair the module installation and retry.' } } if ([string]$entry.ownership -ne 'module') { return [pscustomobject]@{ Valid = $false Message = 'KDN-PAYLOAD-INCOMPLETE: Installed Kaden payload has a non-module managed-file entry. Repair the module installation and retry.' } } if ($digestValue -notmatch '^[0-9a-f]{64}$') { return [pscustomobject]@{ Valid = $false Message = 'KDN-PAYLOAD-INCOMPLETE: Installed Kaden payload has an invalid managed-file digest. Repair the module installation and retry.' } } $sourcePaths[$sourceKey] = $true $destinationPaths[$destinationKey] = $true try { $source = Resolve-KadenContainedPath -Root $PayloadRoot -RelativePath $sourceRelativePath } catch { return [pscustomobject]@{ Valid = $false Message = "KDN-PAYLOAD-INCOMPLETE: $($_.Exception.Message)" } } if (-not (Test-Path -LiteralPath $source)) { return [pscustomobject]@{ Valid = $false Message = "KDN-PAYLOAD-INCOMPLETE: Installed Kaden payload is incomplete (missing '$($entry.source)'). Repair the module installation and retry." } } $digest = Get-KadenFileDigest -LiteralPath $source if ($digest -ne [string]$entry.digest) { return [pscustomobject]@{ Valid = $false Message = "KDN-PAYLOAD-INCOMPLETE: Installed Kaden payload digest mismatch for '$($entry.source)'. Repair the module installation and retry." } } } $expectedPayloadDigest = Get-KadenManifestPayloadDigest -Entries @($manifest.files) if ([string]$manifest.payloadDigest -ne $expectedPayloadDigest) { return [pscustomobject]@{ Valid = $false Message = 'KDN-PAYLOAD-INCOMPLETE: Installed Kaden payload digest does not match its managed-file manifest. Repair the module installation and retry.' } } return [pscustomobject]@{ Valid = $true Message = 'Payload ready.' Manifest = $manifest } } function ConvertTo-KadenNormalizedManifestPath { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [AllowEmptyString()] [string]$RelativePath ) $segments = @($RelativePath -split '[\\/]' | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) if ($segments.Count -lt 1 -or $segments -contains '.' -or $segments -contains '..') { throw "KDN-TXN-PATH-ESCAPE: managed path is not normalized ('$RelativePath')." } return (($segments -join '/').ToLowerInvariant()) } function Get-KadenManifestPayloadDigest { [CmdletBinding()] param( [Parameter(Mandatory = $true)] $Entries ) $lines = @( $Entries | Sort-Object -Property destination | ForEach-Object { [string]$_.destination + "`t" + [string]$_.digest } ) $bytes = [System.Text.Encoding]::UTF8.GetBytes((($lines -join "`n") + "`n")) $sha = [System.Security.Cryptography.SHA256]::Create() try { return ([System.BitConverter]::ToString($sha.ComputeHash($bytes)) -replace '-', '').ToLowerInvariant() } finally { $sha.Dispose() } } |