prism-inference 0.1.21

Bounded Rust CPU inference and tokenization for Prism-compatible GGUF models
#requires -Version 7.6
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $true
Import-Module (Join-Path $PSScriptRoot 'Bonsai.Common.psm1')

function Initialize-BonsaiPrismNativeTree {
    <# .SYNOPSIS
    Copies native inputs once, then verifies the complete cache without changing timestamps.
    #>
    [CmdletBinding(SupportsShouldProcess)]
    [OutputType([string])]
    param(
        [string]$SourceDirectory,
        [string]$BuildDirectory,
        [AllowEmptyCollection()][IO.FileSystemInfo[]]$Entries,
        [ValidateSet('source', 'adapter')][string]$Kind
    )
    $cached = Join-Path $BuildDirectory $Kind
    $null = Resolve-ExternalPath $cached
    if (-not [IO.Directory]::Exists($cached)) {
        if (Test-Path -LiteralPath $cached) { throw "The cached Prism $Kind path is not a directory." }
        if ($Kind -eq 'source' -and [IO.Directory]::Exists($BuildDirectory) -and
            @(Get-ChildItem -LiteralPath $BuildDirectory -Force).Count) {
            throw 'Native build outputs exist without their verified source tree; use a fresh native build cache.'
        }
        if (-not $PSCmdlet.ShouldProcess($cached, "Copy verified Prism $Kind inputs for native build reuse")) { return }
        [void][IO.Directory]::CreateDirectory($cached)
        foreach ($entry in $Entries) {
            $destination = Join-Path $cached ([IO.Path]::GetRelativePath($SourceDirectory, $entry.FullName))
            if ($entry.PSIsContainer) { [void][IO.Directory]::CreateDirectory($destination) }
            else {
                [void][IO.Directory]::CreateDirectory([IO.Path]::GetDirectoryName($destination))
                [IO.File]::Copy($entry.FullName, $destination, $false)
                [IO.File]::SetLastWriteTimeUtc($destination, $entry.LastWriteTimeUtc)
            }
        }
        # Preserve the source mirror's post-copy path check without repeating
        # ancestor walks when an existing cache is reused.
        if ($Kind -eq 'source') { $null = Resolve-ExternalPath $cached }
    }
    $cachedEntries = @(Get-ChildItem -LiteralPath $cached -Force -Recurse)
    if ($cachedEntries.Where({ $_.Attributes -band [IO.FileAttributes]::ReparsePoint }, 'First').Count) {
        throw "Cached Prism $Kind trees cannot contain reparse points."
    }
    $comparer = if ($IsWindows) { [StringComparer]::OrdinalIgnoreCase } else { [StringComparer]::Ordinal }
    $files = [Collections.Generic.Dictionary[string, IO.FileInfo]]::new($comparer)
    foreach ($entry in $cachedEntries) {
        if (-not $entry.PSIsContainer) { $files.Add([IO.Path]::GetRelativePath($cached, $entry.FullName), $entry) }
    }
    $originals = @($Entries | Where-Object { -not $_.PSIsContainer })
    if ($files.Count -ne $originals.Count) { throw "Cached Prism $Kind inventory changed; use a fresh native build cache." }
    foreach ($entry in $originals) {
        $relative = [IO.Path]::GetRelativePath($SourceDirectory, $entry.FullName)
        if (-not $files.ContainsKey($relative) -or $files[$relative].Length -ne $entry.Length -or
            (Get-FileHash -LiteralPath $files[$relative].FullName -Algorithm SHA256).Hash -cne
            (Get-FileHash -LiteralPath $entry.FullName -Algorithm SHA256).Hash) {
            throw "Cached Prism $Kind identity changed: $relative. Use a fresh native build cache."
        }
    }
    return $cached
}

function Initialize-BonsaiPrismNativeSource {
    <#
    .SYNOPSIS
    Verifies a stable native source tree against a freshly verified archive extraction.
    .DESCRIPTION
    Every source file is checked before CMake may reuse native objects. Unchanged
    files retain their timestamps and paths. A stale or modified tree fails closed.
    #>
    [CmdletBinding(SupportsShouldProcess)]
    [OutputType([string])]
    param(
        [Parameter(Mandatory)][string]$SourceDirectory,
        [Parameter(Mandatory)][string]$BuildDirectory
    )
    $source = Resolve-ExternalPath $SourceDirectory
    $build = Resolve-ExternalPath $BuildDirectory
    $separator = [IO.Path]::DirectorySeparatorChar
    $comparison = if ($IsWindows) { [StringComparison]::OrdinalIgnoreCase } else { [StringComparison]::Ordinal }
    if ($source.Equals($build, $comparison) -or $source.StartsWith($build + $separator, $comparison) -or
        $build.StartsWith($source + $separator, $comparison)) {
        throw 'Verified source and native build cache must use separate directories.'
    }
    if (-not [IO.Directory]::Exists($source)) { throw 'The freshly verified Prism source tree is missing.' }
    $sourceEntries = @(Get-ChildItem -LiteralPath $source -Force -Recurse)
    if ($sourceEntries.Where({ $_.Attributes -band [IO.FileAttributes]::ReparsePoint }, 'First').Count) {
        throw 'Prism source trees cannot contain reparse points.'
    }
    return Initialize-BonsaiPrismNativeTree -SourceDirectory $source -BuildDirectory $build -Entries $sourceEntries -Kind source
}

function Initialize-BonsaiPrismNativeAdapter {
    <#
    .SYNOPSIS
    Keeps verified adapter build inputs at stable paths and timestamps.
    .DESCRIPTION
    Fresh checkouts must not invalidate a cached native bridge's translation
    unit and headers. Copy only CMake/native/Rust inputs; then compare the complete
    cached inventory and every file's bytes with the current checkout. Modified,
    missing or injected cached files fail closed and require a fresh cache.
    #>
    [CmdletBinding(SupportsShouldProcess)]
    [OutputType([string])]
    param(
        [Parameter(Mandatory)][string]$AdapterDirectory,
        [Parameter(Mandatory)][string]$BuildDirectory
    )
    if (-not [IO.Path]::IsPathFullyQualified($AdapterDirectory) -or $AdapterDirectory -match '%[^%]+%') {
        throw 'AdapterDirectory must be an expanded absolute directory.'
    }
    $source = [IO.Path]::GetFullPath($AdapterDirectory)
    $build = Resolve-ExternalPath $BuildDirectory
    $comparison = if ($IsWindows) { [StringComparison]::OrdinalIgnoreCase } else { [StringComparison]::Ordinal }
    $separator = [IO.Path]::DirectorySeparatorChar
    if ($source.Equals($build, $comparison) -or $source.StartsWith($build + $separator, $comparison) -or
        $build.StartsWith($source.TrimEnd($separator) + $separator, $comparison)) {
        throw 'Adapter sources and native build cache must use separate directories.'
    }
    if (-not [IO.Directory]::Exists($source)) { throw 'The current Prism adapter source tree is missing.' }
    $cursor = $source
    while ($cursor) {
        if ((Get-Item -LiteralPath $cursor -Force).Attributes -band [IO.FileAttributes]::ReparsePoint) {
            throw 'Prism adapter source paths cannot contain reparse points.'
        }
        $cursor = [IO.Path]::GetDirectoryName($cursor)
    }
    $sourceEntries = @(Get-ChildItem -LiteralPath $source -Force -Recurse)
    if ($sourceEntries.Where({ $_.Attributes -band [IO.FileAttributes]::ReparsePoint }, 'First').Count) {
        throw 'Prism adapter source trees cannot contain reparse points.'
    }
    $originals = @($sourceEntries | Where-Object {
        -not $_.PSIsContainer -and ($_.Name -ceq 'CMakeLists.txt' -or $_.Extension -cin @('.c', '.cpp', '.h', '.hpp', '.inc', '.cmake', '.rs'))
    })
    if (-not [IO.File]::Exists((Join-Path $source 'CMakeLists.txt')) -or $originals.Count -eq 0) {
        throw 'The Prism adapter has no native CMake inputs.'
    }
    return Initialize-BonsaiPrismNativeTree -SourceDirectory $source -BuildDirectory $build -Entries $originals -Kind adapter
}

Export-ModuleMember -Function Initialize-BonsaiPrismNativeSource, Initialize-BonsaiPrismNativeAdapter