Support/Package/Schema/Eigenverft.Manifested.Package.Package.DefinitionReference.ps1

<#
    Eigenverft.Manifested.Package.Package.DefinitionReference
#>


function Get-PackageDefinitionPublication {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$DefinitionDocument,

        [Parameter(Mandatory = $true)]
        [string]$DefinitionPath
    )

    if (-not $DefinitionDocument.PSObject.Properties['definitionPublication']) {
        throw "Package definition '$DefinitionPath' is missing required definitionPublication metadata."
    }

    $publication = $DefinitionDocument.definitionPublication
    foreach ($requiredProperty in @('publisherId', 'publisherName', 'definitionId', 'definitionRevision', 'publishedAtUtc')) {
        if (-not $publication.PSObject.Properties[$requiredProperty] -or [string]::IsNullOrWhiteSpace([string]$publication.$requiredProperty)) {
            throw "Package definition '$DefinitionPath' is missing definitionPublication.$requiredProperty."
        }
    }

    return [pscustomobject]@{
        PublisherId        = [string]$publication.publisherId
        PublisherName      = [string]$publication.publisherName
        DefinitionId       = [string]$publication.definitionId
        DefinitionRevision = [int]$publication.definitionRevision
        PublishedAtUtc     = [string]$publication.publishedAtUtc
    }
}

function Get-PackageLocalDefinitionPath {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('Candidate', 'Assigned')]
        [string]$Role,

        [Parameter(Mandatory = $true)]
        [string]$LocalEndpointRoot,

        [Parameter(Mandatory = $true)]
        [string]$PublisherId,

        [AllowNull()]
        [string]$DefinitionId = $null
    )

    $safePublisherId = ConvertTo-PackageSafePathSegment -Value $PublisherId
    $safeDefinitionId = ConvertTo-PackageSafePathSegment -Value $DefinitionId
    return [System.IO.Path]::GetFullPath((Join-Path (Join-Path (Join-Path $LocalEndpointRoot $Role) $safePublisherId) ($safeDefinitionId + '.json')))
}

function Copy-PackageDefinitionToLocalDefinitionStore {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('Candidate', 'Assigned')]
        [string]$Role,

        [Parameter(Mandatory = $true)]
        [string]$SourcePath,

        [Parameter(Mandatory = $true)]
        [string]$LocalEndpointRoot,

        [Parameter(Mandatory = $true)]
        [string]$PublisherId,

        [Parameter(Mandatory = $true)]
        [string]$DefinitionId,

        [Parameter(Mandatory = $true)]
        [int]$DefinitionRevision
    )

    $targetPath = Get-PackageLocalDefinitionPath -Role $Role -LocalEndpointRoot $LocalEndpointRoot -PublisherId $PublisherId -DefinitionId $DefinitionId
    $targetDirectory = Split-Path -Parent $targetPath
    $null = New-Item -ItemType Directory -Path $targetDirectory -Force

    $sourceHash = Get-PackageFileSha256 -Path $SourcePath
    $targetExists = Test-Path -LiteralPath $targetPath -PathType Leaf
    if ($targetExists) {
        $targetHash = Get-PackageFileSha256 -Path $targetPath
        if ([string]::Equals($sourceHash, $targetHash, [System.StringComparison]::OrdinalIgnoreCase)) {
            return [pscustomobject]@{
                Path          = $targetPath
                Hash          = $targetHash
                Status        = 'Reused'
                RevisionReuse = $false
            }
        }

        try {
            $existingInfo = Read-PackageJsonDocument -Path $targetPath
            $existingPublication = Get-PackageDefinitionPublication -DefinitionDocument $existingInfo.Document -DefinitionPath $targetPath
            if ($existingPublication.DefinitionRevision -eq $DefinitionRevision) {
                Write-PackageExecutionMessage -Level 'WRN' -Message ("[WARN] Package definition publisher '{0}' reused definitionRevision '{1}' for definition '{2}' with different content; updating local {3} materialized copy." -f $PublisherId, $DefinitionRevision, $DefinitionId, $Role)
            }
        }
        catch {
            Write-PackageExecutionMessage -Level 'WRN' -Message ("[WARN] Existing local {0} definition copy '{1}' could not be inspected before replacement: {2}" -f $Role, $targetPath, $_.Exception.Message)
        }
    }

    Copy-FileToPath -SourcePath $SourcePath -TargetPath $targetPath -Overwrite | Out-Null
    return [pscustomobject]@{
        Path          = $targetPath
        Hash          = Get-PackageFileSha256 -Path $targetPath
        Status        = if ($targetExists) { 'Updated' } else { 'Copied' }
        RevisionReuse = $targetExists
    }
}

function Get-PackageDefinitionJsonPathsUnderDirectory {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$DirectoryPath
    )

    if (-not (Test-Path -LiteralPath $DirectoryPath -PathType Container)) {
        return @()
    }

    return @(Get-ChildItem -LiteralPath $DirectoryPath -Filter '*.json' -File -Recurse | Select-Object -ExpandProperty FullName)
}

function Select-PackageDefinitionCandidatesFromEndpointScanRoot {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$EndpointName,

        [Parameter(Mandatory = $true)]
        [psobject]$EndpointSource,

        [Parameter(Mandatory = $true)]
        [string]$ScanRootPath,

        [AllowNull()]
        [psobject]$TrustInventoryDocument = $null,

        [AllowNull()]
        [string]$DefinitionId = $null
    )

    $candidates = New-Object System.Collections.Generic.List[object]
    foreach ($definitionPath in @(Get-PackageDefinitionJsonPathsUnderDirectory -DirectoryPath $ScanRootPath)) {
        try {
            $definitionInfo = Read-PackageJsonDocument -Path $definitionPath
            $definition = $definitionInfo.Document
            $publication = Get-PackageDefinitionPublication -DefinitionDocument $definition -DefinitionPath $definitionPath
            $docDefinitionId = [string]$publication.DefinitionId
            if ([string]::IsNullOrWhiteSpace($docDefinitionId) -or
                (-not [string]::IsNullOrWhiteSpace($DefinitionId) -and
                -not [string]::Equals($docDefinitionId, $DefinitionId, [System.StringComparison]::OrdinalIgnoreCase))) {
                continue
            }

            $signatureInfo = Test-PackageDefinitionSignatureDocument -Definition $definition -TrustInventoryDocument $TrustInventoryDocument
            $candidates.Add([pscustomobject]@{
                EndpointName               = $EndpointName
                EndpointSourceKind         = [string]$EndpointSource.kind
                DefinitionScanRootPath     = $ScanRootPath
                DefinitionId               = $docDefinitionId
                DefinitionPath             = [System.IO.Path]::GetFullPath($definitionPath)
                PublisherId                = [string]$publication.PublisherId
                PublisherName              = [string]$publication.PublisherName
                DefinitionRevision         = [int]$publication.DefinitionRevision
                PublishedAtUtc             = [string]$publication.PublishedAtUtc
                SourceHash                 = Get-PackageFileSha256 -Path $definitionPath
                SignatureStatus            = [string]$signatureInfo.Status
                SignatureValid             = [bool]$signatureInfo.Valid
                SignatureTrusted           = [bool]$signatureInfo.Trusted
                SignatureKeyThumbprint     = if ($signatureInfo.PSObject.Properties['KeyThumbprint']) { [string]$signatureInfo.KeyThumbprint } else { $null }
                SignatureSignerDisplayName = if ($signatureInfo.PSObject.Properties['SignerDisplayName']) { [string]$signatureInfo.SignerDisplayName } else { $null }
                SignatureCertificateSubject = if ($signatureInfo.PSObject.Properties['CertificateSubject']) { [string]$signatureInfo.CertificateSubject } else { $null }
                SignatureCertificatePem    = if ($signatureInfo.PSObject.Properties['CertificatePem']) { [string]$signatureInfo.CertificatePem } else { $null }
                SignatureCertificateSource = if ($signatureInfo.PSObject.Properties['CertificateSource']) { [string]$signatureInfo.CertificateSource } else { $null }
                SignatureCertificateNotBeforeUtc = if ($signatureInfo.PSObject.Properties['CertificateNotBeforeUtc']) { [string]$signatureInfo.CertificateNotBeforeUtc } else { $null }
                SignatureCertificateNotAfterUtc = if ($signatureInfo.PSObject.Properties['CertificateNotAfterUtc']) { [string]$signatureInfo.CertificateNotAfterUtc } else { $null }
                SignatureTrustEntryFound   = if ($signatureInfo.PSObject.Properties['TrustEntryFound']) { [bool]$signatureInfo.TrustEntryFound } else { $false }
                SignatureTrustEntryPublisherMatches = if ($signatureInfo.PSObject.Properties['TrustEntryPublisherMatches']) { [bool]$signatureInfo.TrustEntryPublisherMatches } else { $false }
                SignatureCanonicalContentHash = if ($signatureInfo.PSObject.Properties['CanonicalContentHash']) { [string]$signatureInfo.CanonicalContentHash } else { $null }
                SignatureErrorMessage      = if ($signatureInfo.PSObject.Properties['ErrorMessage']) { [string]$signatureInfo.ErrorMessage } else { $null }
            }) | Out-Null
        }
        catch {
            Write-PackageExecutionMessage -Level 'WRN' -Message ("[WARN] Skipped package definition candidate '{0}' from endpoint '{1}': {2}" -f $definitionPath, $EndpointName, $_.Exception.Message)
        }
    }

    return @($candidates.ToArray())
}

function Get-PackageEnabledEndpointSources {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$EndpointInventoryDocument
    )

    $sources = @(
        foreach ($source in @(Get-PackageEndpointSourceEntries -Document $EndpointInventoryDocument)) {
            if (-not [bool]$source.enabled) {
                continue
            }
            $endpointName = [string]$source.endpointName
            [pscustomobject]@{
                EndpointName = $endpointName
                Source       = $source
                SearchOrder  = if ($source.PSObject.Properties['searchOrder']) { [int]$source.searchOrder } else { 1000 }
            }
        }
    )

    return @($sources | Sort-Object -Property SearchOrder, EndpointName)
}

function Get-PackageDefinitionCandidateRows {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [object[]]$SourceRows,

        [AllowNull()]
        [string]$ApplicationRootDirectory = $null,

        [AllowNull()]
        [psobject]$TrustInventoryDocument = $null,

        [AllowNull()]
        [string]$DefinitionId = $null
    )

    $candidateRows = New-Object System.Collections.Generic.List[object]
    foreach ($sourceRow in @($SourceRows)) {
        $endpointName = [string]$sourceRow.EndpointName
        $scanRootPath = Resolve-PackageEndpointRootPath -EndpointName $endpointName -Source $sourceRow.Source -ApplicationRootDirectory $ApplicationRootDirectory
        foreach ($candidate in @(Select-PackageDefinitionCandidatesFromEndpointScanRoot -EndpointName $endpointName -EndpointSource $sourceRow.Source -ScanRootPath $scanRootPath -TrustInventoryDocument $TrustInventoryDocument -DefinitionId $DefinitionId)) {
            $candidate | Add-Member -MemberType NoteProperty -Name EndpointSearchOrder -Value ([int]$sourceRow.SearchOrder) -Force
            $candidateRows.Add($candidate) | Out-Null
        }
    }

    return @($candidateRows.ToArray())
}

function Test-PackageCatalogTrustPublisherIdListed {
    [CmdletBinding()]
    param(
        [AllowNull()]
        [string[]]$PublisherIds = @(),

        [AllowNull()]
        [string]$PublisherId = $null
    )

    if ([string]::IsNullOrWhiteSpace($PublisherId)) {
        return $false
    }

    foreach ($configuredPublisherId in @($PublisherIds)) {
        if ([string]::IsNullOrWhiteSpace([string]$configuredPublisherId)) {
            continue
        }
        if ([string]::Equals(([string]$configuredPublisherId).Trim(), $PublisherId, [System.StringComparison]::OrdinalIgnoreCase)) {
            return $true
        }
    }

    return $false
}

function Resolve-PackageDefinitionCandidateTrustEligibility {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$Candidate,

        [ValidateSet('strict', 'allowUnsigned')]
        [string]$CatalogTrustPolicy = 'strict',

        [AllowNull()]
        [string[]]$CatalogTrustAllowUnsignedPublisherIds = @(),

        [AllowNull()]
        [string[]]$CatalogTrustBlockedPublisherIds = @(),

        [ValidateSet('fail', 'prompt', 'trust')]
        [string]$UnknownSignedKeyPolicy = 'prompt'
    )

    $publisherId = if ($Candidate.PSObject.Properties['PublisherId']) { [string]$Candidate.PublisherId } else { $null }
    $signatureStatus = if ($Candidate.PSObject.Properties['SignatureStatus'] -and
        -not [string]::IsNullOrWhiteSpace([string]$Candidate.SignatureStatus)) {
        [string]$Candidate.SignatureStatus
    }
    else {
        'missingSignature'
    }

    if (Test-PackageCatalogTrustPublisherIdListed -PublisherIds $CatalogTrustBlockedPublisherIds -PublisherId $publisherId) {
        return [pscustomobject]@{
            Eligible    = $false
            TrustStatus = 'blockedPublisher'
            TrustReason = "Publisher '$publisherId' is blocked by catalogTrust.blockedPublisherIds."
        }
    }

    if ($Candidate.PSObject.Properties['SignatureTrusted'] -and [bool]$Candidate.SignatureTrusted) {
        return [pscustomobject]@{
            Eligible    = $true
            TrustStatus = 'signedTrusted'
            TrustReason = 'Definition signature is valid and the signing key is trusted for this publisher.'
        }
    }

    $hasEmbeddedCertificate = $Candidate.PSObject.Properties['SignatureCertificatePem'] -and
        -not [string]::IsNullOrWhiteSpace([string]$Candidate.SignatureCertificatePem)
    $hasExistingTrustEntry = $Candidate.PSObject.Properties['SignatureTrustEntryFound'] -and [bool]$Candidate.SignatureTrustEntryFound
    if ($Candidate.PSObject.Properties['SignatureValid'] -and [bool]$Candidate.SignatureValid -and
        [string]::Equals($signatureStatus, 'validUntrusted', [System.StringComparison]::OrdinalIgnoreCase) -and
        $hasEmbeddedCertificate -and
        -not $hasExistingTrustEntry -and
        $UnknownSignedKeyPolicy -in @('prompt', 'trust')) {
        return [pscustomobject]@{
            Eligible    = $true
            TrustStatus = if ([string]::Equals($UnknownSignedKeyPolicy, 'trust', [System.StringComparison]::OrdinalIgnoreCase)) { 'signedUnknownKeyAutoTrust' } else { 'signedUnknownKeyPrompt' }
            TrustReason = "Definition signature is valid with an embedded certificate, but the signing key is not yet trusted for publisher '$publisherId'."
        }
    }

    $isUnsignedDefinition = $signatureStatus -in @('missingSignature', 'unsigned')
    $publisherAllowsUnsigned = Test-PackageCatalogTrustPublisherIdListed -PublisherIds $CatalogTrustAllowUnsignedPublisherIds -PublisherId $publisherId

    if ([string]::Equals($CatalogTrustPolicy, 'allowUnsigned', [System.StringComparison]::OrdinalIgnoreCase) -and
        $isUnsignedDefinition -and $publisherAllowsUnsigned) {
        return [pscustomobject]@{
            Eligible    = $true
            TrustStatus = 'unsignedConfigTrust'
            TrustReason = "Unsigned definition is allowed because catalogTrust.policy='allowUnsigned' and publisher '$publisherId' is listed in catalogTrust.allowUnsignedPublisherIds."
        }
    }

    $reason = switch -Exact ($signatureStatus) {
        'missingSignature' {
            if ([string]::Equals($CatalogTrustPolicy, 'strict', [System.StringComparison]::OrdinalIgnoreCase)) {
                "catalogTrust.policy='strict' requires definitionPublication.definitionSignature.kind='signed'."
            }
            elseif (-not $publisherAllowsUnsigned) {
                "Unsigned definition is not allowed because publisher '$publisherId' is not listed in catalogTrust.allowUnsignedPublisherIds."
            }
            else {
                'definitionPublication.definitionSignature is missing.'
            }
        }
        'unsigned' {
            if ([string]::Equals($CatalogTrustPolicy, 'strict', [System.StringComparison]::OrdinalIgnoreCase)) {
                "catalogTrust.policy='strict' rejects unsigned definitions."
            }
            elseif (-not $publisherAllowsUnsigned) {
                "Unsigned definition is not allowed because publisher '$publisherId' is not listed in catalogTrust.allowUnsignedPublisherIds."
            }
            else {
                'Unsigned definition was not allowed by catalog policy.'
            }
        }
        'unknownKey' { 'Definition signing key is not trusted or its certificate is unavailable.' }
        'validUntrusted' { 'Definition signature is valid, but the signing key is not trusted for this publisher.' }
        'revokedKey' { 'Definition signing key is revoked.' }
        'invalidEmbeddedCertificate' { 'Definition embedded signing certificate is not valid PEM.' }
        'certificateThumbprintMismatch' { 'Definition embedded signing certificate thumbprint does not match definitionSignature.keyThumbprint.' }
        'invalidSignature' { 'Definition signature verification failed.' }
        'invalidSignatureValue' { 'Definition signature value is not valid base64.' }
        'missingSignatureValue' { 'Signed definition is missing definitionSignature.signatureValue.' }
        'unsupportedSignatureKind' { 'Definition uses an unsupported definitionSignature.kind.' }
        'unsupportedSignatureFormat' { 'Definition uses an unsupported definitionSignature.format.' }
        default { "Definition signature status '$signatureStatus' is not eligible." }
    }

    return [pscustomobject]@{
        Eligible    = $false
        TrustStatus = $signatureStatus
        TrustReason = $reason
    }
}

function Select-PackageDefinitionCandidateWinner {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [object[]]$Candidates,

        [Parameter(Mandatory = $true)]
        [string]$DefinitionId,

        [AllowNull()]
        [string]$PublisherId = $null,

        [ValidateSet('strict', 'allowUnsigned')]
        [string]$CatalogTrustPolicy = 'strict',

        [AllowNull()]
        [string[]]$CatalogTrustAllowUnsignedPublisherIds = @(),

        [AllowNull()]
        [string[]]$CatalogTrustBlockedPublisherIds = @(),

        [ValidateSet('fail', 'prompt', 'trust')]
        [string]$UnknownSignedKeyPolicy = 'prompt',

        [ValidateSet('fail', 'warnFirst', 'first', 'warnLast', 'last')]
        [string]$DefinitionPublisherConflictMode = 'fail'
    )

    $candidateSet = @($Candidates)
    if (-not [string]::IsNullOrWhiteSpace($PublisherId)) {
        $candidateSet = @($candidateSet | Where-Object {
                [string]::Equals([string]$_.PublisherId, [string]$PublisherId, [System.StringComparison]::OrdinalIgnoreCase)
            })
        if ($candidateSet.Count -eq 0) {
            throw "Package definition '$DefinitionId' was found in enabled endpoints, but not for publisher '$PublisherId'. -PublisherId filters definitionPublication.publisherId."
        }
    }

    $ineligibleReasons = New-Object System.Collections.Generic.List[string]
    $trustedCandidates = @(
        foreach ($candidate in @($candidateSet)) {
            $eligibility = Resolve-PackageDefinitionCandidateTrustEligibility -Candidate $candidate -CatalogTrustPolicy $CatalogTrustPolicy -CatalogTrustAllowUnsignedPublisherIds $CatalogTrustAllowUnsignedPublisherIds -CatalogTrustBlockedPublisherIds $CatalogTrustBlockedPublisherIds -UnknownSignedKeyPolicy $UnknownSignedKeyPolicy
            $candidate | Add-Member -MemberType NoteProperty -Name CatalogTrustPolicy -Value ([string]$CatalogTrustPolicy) -Force
            $candidate | Add-Member -MemberType NoteProperty -Name CatalogTrustStatus -Value ([string]$eligibility.TrustStatus) -Force
            $candidate | Add-Member -MemberType NoteProperty -Name CatalogTrustReason -Value ([string]$eligibility.TrustReason) -Force
            if (-not [bool]$eligibility.Eligible) {
                $ineligibleReasons.Add(("{0}: {1}" -f [string]$candidate.PublisherId, [string]$eligibility.TrustReason)) | Out-Null
                continue
            }
            $candidate
        }
    )

    if ($trustedCandidates.Count -eq 0) {
        $suffix = if ([string]::IsNullOrWhiteSpace($PublisherId)) { '' } else { " for publisher '$PublisherId'" }
        $reasonText = (@($ineligibleReasons.ToArray()) | Select-Object -Unique) -join '; '
        if ([string]::IsNullOrWhiteSpace($reasonText)) {
            $reasonText = 'No matching candidate satisfied catalog trust.'
        }
        throw "Package definition '$DefinitionId' was found but no candidate satisfied catalog trust policy '$CatalogTrustPolicy'$suffix. $reasonText"
    }

    if ([string]::IsNullOrWhiteSpace($PublisherId)) {
        $publisherIds = @($trustedCandidates | Select-Object -ExpandProperty PublisherId -Unique)
        if ($publisherIds.Count -gt 1) {
            if ([string]::Equals($DefinitionPublisherConflictMode, 'fail', [System.StringComparison]::OrdinalIgnoreCase)) {
                throw "Package definition '$DefinitionId' is provided by multiple eligible publisherIds: $($publisherIds -join ', '). Use -PublisherId or set package.endpointEnvironment.defaults.definitionPublisherConflictMode."
            }

            $descending = $DefinitionPublisherConflictMode -in @('warnLast', 'last')
            $selectedByEndpoint = if ($descending) {
                @($trustedCandidates | Sort-Object -Property EndpointSearchOrder, EndpointName, DefinitionPath -Descending | Select-Object -First 1)[0]
            }
            else {
                @($trustedCandidates | Sort-Object -Property EndpointSearchOrder, EndpointName, DefinitionPath | Select-Object -First 1)[0]
            }
            $selectedPublisherId = [string]$selectedByEndpoint.PublisherId
            if ($DefinitionPublisherConflictMode -in @('warnFirst', 'warnLast')) {
                Write-PackageExecutionMessage -Level 'WRN' -Message ("[WARN] Package definition '{0}' is provided by multiple eligible publisherIds ({1}); definitionPublisherConflictMode='{2}' selected publisher '{3}' from endpoint '{4}'." -f $DefinitionId, ($publisherIds -join ', '), $DefinitionPublisherConflictMode, $selectedPublisherId, [string]$selectedByEndpoint.EndpointName)
            }
            $trustedCandidates = @($trustedCandidates | Where-Object { [string]::Equals([string]$_.PublisherId, $selectedPublisherId, [System.StringComparison]::OrdinalIgnoreCase) })
        }
    }

    $bestRevision = (@($trustedCandidates) | Measure-Object -Property DefinitionRevision -Maximum).Maximum
    $bestRevisionCandidates = @($trustedCandidates | Where-Object { [int]$_.DefinitionRevision -eq [int]$bestRevision })
    $bestHashes = @($bestRevisionCandidates | Select-Object -ExpandProperty SourceHash -Unique)
    if ($bestHashes.Count -gt 1) {
        $locations = (@($bestRevisionCandidates) | ForEach-Object { "'$($_.EndpointName):$($_.DefinitionPath) hash=$($_.SourceHash)'" }) -join ', '
        throw "Package definition '$DefinitionId' publisher '$($bestRevisionCandidates[0].PublisherId)' reused definitionRevision '$bestRevision' with different content across endpoints. Matching candidates: $locations. Publish a higher revision or disable one endpoint."
    }

    return @($bestRevisionCandidates | Sort-Object -Property EndpointSearchOrder, EndpointName, DefinitionPath | Select-Object -First 1)[0]
}

function Sync-PackageEndpointCandidateDefinitions {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [object[]]$SourceRows,

        [AllowNull()]
        [psobject]$TrustInventoryDocument = $null,

        [AllowNull()]
        [string]$ApplicationRootDirectory = $null,

        [Parameter(Mandatory = $true)]
        [string]$LocalEndpointRoot,

        [ValidateSet('strict', 'allowUnsigned')]
        [string]$CatalogTrustPolicy = 'strict',

        [AllowNull()]
        [string[]]$CatalogTrustAllowUnsignedPublisherIds = @(),

        [AllowNull()]
        [string[]]$CatalogTrustBlockedPublisherIds = @(),

        [ValidateSet('fail', 'prompt', 'trust')]
        [string]$UnknownSignedKeyPolicy = 'prompt',

        [ValidateSet('fail', 'warnFirst', 'first', 'warnLast', 'last')]
        [string]$DefinitionPublisherConflictMode = 'fail'
    )

    $allCandidates = @(Get-PackageDefinitionCandidateRows -SourceRows $SourceRows -ApplicationRootDirectory $ApplicationRootDirectory -TrustInventoryDocument $TrustInventoryDocument)
    $keys = @($allCandidates | ForEach-Object { [string]$_.DefinitionId } | Sort-Object -Unique)
    $materializedCount = 0
    foreach ($definitionId in @($keys)) {
        try {
            $winner = Select-PackageDefinitionCandidateWinner -Candidates @($allCandidates | Where-Object { [string]::Equals([string]$_.DefinitionId, [string]$definitionId, [System.StringComparison]::OrdinalIgnoreCase) }) -DefinitionId $definitionId -CatalogTrustPolicy $CatalogTrustPolicy -CatalogTrustAllowUnsignedPublisherIds $CatalogTrustAllowUnsignedPublisherIds -CatalogTrustBlockedPublisherIds $CatalogTrustBlockedPublisherIds -UnknownSignedKeyPolicy $UnknownSignedKeyPolicy -DefinitionPublisherConflictMode $DefinitionPublisherConflictMode
            Copy-PackageDefinitionToLocalDefinitionStore -Role 'Candidate' -SourcePath $winner.DefinitionPath -LocalEndpointRoot $LocalEndpointRoot -PublisherId ([string]$winner.PublisherId) -DefinitionId ([string]$winner.DefinitionId) -DefinitionRevision ([int]$winner.DefinitionRevision) | Out-Null
            $materializedCount++
        }
        catch {
            Write-PackageExecutionMessage -Level 'WRN' -Message ("[WARN] Skipped endpoint-wide Candidate materialization for definition '{0}': {1}" -f $definitionId, $_.Exception.Message)
        }
    }

    return $materializedCount
}

function Confirm-PackageUnknownSigningKeyTrust {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$Candidate
    )

    $keyText = if ([string]::IsNullOrWhiteSpace([string]$Candidate.SignatureKeyThumbprint)) { '<none>' } else { [string]$Candidate.SignatureKeyThumbprint }
    $signerText = if ([string]::IsNullOrWhiteSpace([string]$Candidate.SignatureSignerDisplayName)) { '<none>' } else { [string]$Candidate.SignatureSignerDisplayName }
    $subjectText = if ([string]::IsNullOrWhiteSpace([string]$Candidate.SignatureCertificateSubject)) { '<none>' } else { [string]$Candidate.SignatureCertificateSubject }
    $notBeforeText = if ([string]::IsNullOrWhiteSpace([string]$Candidate.SignatureCertificateNotBeforeUtc)) { '<unknown>' } else { [string]$Candidate.SignatureCertificateNotBeforeUtc }
    $notAfterText = if ([string]::IsNullOrWhiteSpace([string]$Candidate.SignatureCertificateNotAfterUtc)) { '<unknown>' } else { [string]$Candidate.SignatureCertificateNotAfterUtc }
    $hashText = if ([string]::IsNullOrWhiteSpace([string]$Candidate.SignatureCanonicalContentHash)) { '<none>' } else { [string]$Candidate.SignatureCanonicalContentHash }

    $message = @(
        "Package definition is signed by an unknown signing key. The signature is valid with the embedded public certificate."
        "Trust is for this signing key and publisher id, and will apply to other package definitions signed by this key for the same publisher."
        ''
        "Publisher: $($Candidate.PublisherId) ($($Candidate.PublisherName))"
        "Definition: $($Candidate.DefinitionId) revision $($Candidate.DefinitionRevision)"
        "Endpoint: $($Candidate.EndpointName)"
        "Path: $($Candidate.DefinitionPath)"
        "Signer: $signerText"
        "Certificate subject: $subjectText"
        "Thumbprint: $keyText"
        "Valid from: $notBeforeText"
        "Valid until: $notAfterText"
        "Canonical content hash: $hashText"
        ''
        "Trust this signing key for publisher '$($Candidate.PublisherId)'?"
    ) -join [Environment]::NewLine

    $choices = [System.Management.Automation.Host.ChoiceDescription[]]@(
        [System.Management.Automation.Host.ChoiceDescription]::new('&Yes', 'Trust this signing key for this publisher and continue.')
        [System.Management.Automation.Host.ChoiceDescription]::new('&No', 'Do not trust this signing key.')
    )

    try {
        return ((Get-Host).UI.PromptForChoice('Trust package signing key', $message, $choices, 1) -eq 0)
    }
    catch {
        Write-PackageExecutionMessage -Level 'WRN' -Message ("[WARN] Could not prompt for package signing-key trust: {0}" -f $_.Exception.Message)
        return $false
    }
}

function Add-PackageTrustForDefinitionCandidate {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$Candidate,

        [Parameter(Mandatory = $true)]
        [psobject]$TrustInventoryDocumentInfo,

        [Parameter(Mandatory = $true)]
        [string]$TrustSource,

        [Parameter(Mandatory = $true)]
        [string]$TrustReason
    )

    if (-not $Candidate.PSObject.Properties['SignatureCertificatePem'] -or [string]::IsNullOrWhiteSpace([string]$Candidate.SignatureCertificatePem)) {
        throw "Package definition '$($Candidate.DefinitionId)' does not contain definitionSignature.certificatePem."
    }

    $certificate = ConvertFrom-PackageCertificatePem -CertificatePem ([string]$Candidate.SignatureCertificatePem)
    try {
        $entry = New-PackageTrustEntry -Certificate $certificate -PublisherId ([string]$Candidate.PublisherId) -PublisherName ([string]$Candidate.PublisherName) -SignerDisplayName ([string]$Candidate.SignatureSignerDisplayName) -TrustSource $TrustSource -TrustReason $TrustReason
        $existing = Get-PackageTrustEntryByThumbprint -Document $TrustInventoryDocumentInfo.Document -KeyThumbprint ([string]$entry.keyThumbprint)
        if ($existing) {
            $existingPublisherId = if ($existing.PSObject.Properties['publisherId']) { [string]$existing.publisherId } else { '<unknown>' }
            throw "Package signing certificate '$($entry.keyThumbprint)' already exists in '$($TrustInventoryDocumentInfo.Path)' for publisher '$existingPublisherId'. Use explicit trust management to change this key."
        }

        $TrustInventoryDocumentInfo.Document.keys = @($TrustInventoryDocumentInfo.Document.keys) + $entry
        Save-PackageTrustInventoryDocument -DocumentInfo $TrustInventoryDocumentInfo
        return Select-PackageTrustSummary -Entry $entry -InventoryPath $TrustInventoryDocumentInfo.Path
    }
    finally {
        $certificate.Dispose()
    }
}

function Resolve-PackageSelectedCandidateUnknownSigningKeyTrust {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$Candidate,

        [Parameter(Mandatory = $true)]
        [psobject]$TrustInventoryDocumentInfo,

        [ValidateSet('fail', 'prompt', 'trust')]
        [string]$UnknownSignedKeyPolicy = 'prompt'
    )

    if ([string]$Candidate.CatalogTrustStatus -notin @('signedUnknownKeyPrompt', 'signedUnknownKeyAutoTrust')) {
        return $Candidate
    }

    if ([string]::Equals($UnknownSignedKeyPolicy, 'prompt', [System.StringComparison]::OrdinalIgnoreCase)) {
        if (-not (Confirm-PackageUnknownSigningKeyTrust -Candidate $Candidate)) {
            throw "Package definition '$($Candidate.DefinitionId)' is signed by unknown key '$($Candidate.SignatureKeyThumbprint)' for publisher '$($Candidate.PublisherId)', and the key was not trusted. Use Import-PackageTrust -Path '<public-cert.cer>' before Invoke-Package, or rerun Invoke-Package with -AcceptUnknownSigningKey."
        }
    }

    $trustSource = if ([string]::Equals($UnknownSignedKeyPolicy, 'trust', [System.StringComparison]::OrdinalIgnoreCase)) { 'invokePackageAutoTrust' } else { 'invokePackagePrompt' }
    $trustReason = "Trusted from embedded certificate while resolving package definition '$($Candidate.DefinitionId)' from endpoint '$($Candidate.EndpointName)'."
    $summary = Add-PackageTrustForDefinitionCandidate -Candidate $Candidate -TrustInventoryDocumentInfo $TrustInventoryDocumentInfo -TrustSource $trustSource -TrustReason $trustReason
    Write-PackageExecutionMessage -Message ("[TRUST] Trusted embedded signing key '{0}' for publisher '{1}'." -f [string]$summary.KeyThumbprint, [string]$summary.PublisherId)

    Set-PackageObjectProperty -InputObject $Candidate -Name 'SignatureTrusted' -Value $true
    Set-PackageObjectProperty -InputObject $Candidate -Name 'SignatureStatus' -Value 'validTrusted'
    Set-PackageObjectProperty -InputObject $Candidate -Name 'CatalogTrustStatus' -Value 'signedTrusted'
    Set-PackageObjectProperty -InputObject $Candidate -Name 'CatalogTrustReason' -Value 'Definition signature is valid and the signing key was trusted from the embedded certificate during this invocation.'
    return $Candidate
}

function Resolve-PackageDefinitionReference {
<#
.SYNOPSIS
Resolves a Package definition identity to a local materialized definition path.
 
.DESCRIPTION
PackageEndpointInventory.json lists scan endpoints. PackageTrustInventory.json and
PackageConfig.catalogTrust decide catalog authority. Matching uses
definitionPublication.definitionId, optional definitionPublication.publisherId,
catalog trust eligibility, conflict policy, and then definitionRevision.
#>

    [CmdletBinding()]
    param(
        [AllowNull()]
        [string]$PublisherId = $null,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$DefinitionId,

        [AllowNull()]
        [string]$ApplicationRootDirectory = $null,

        [AllowNull()]
        [string]$LocalEndpointRoot = $null,

        [ValidateSet('packageFocused', 'endpointFocused')]
        [string]$EndpointMaterializationMode = 'packageFocused',

        [ValidateSet('strict', 'allowUnsigned')]
        [string]$CatalogTrustPolicy = 'strict',

        [AllowNull()]
        [string[]]$CatalogTrustAllowUnsignedPublisherIds = @(),

        [AllowNull()]
        [string[]]$CatalogTrustBlockedPublisherIds = @(),

        [ValidateSet('fail', 'prompt', 'trust')]
        [string]$UnknownSignedKeyPolicy = 'prompt',

        [ValidateSet('fail', 'warnFirst', 'first', 'warnLast', 'last')]
        [string]$DefinitionPublisherConflictMode = 'fail'
    )

    $endpointInventoryInfo = Get-PackageEndpointInventoryInfo
    $trustInventoryInfo = Get-PackageTrustInventoryInfo
    $sourceRows = @(Get-PackageEnabledEndpointSources -EndpointInventoryDocument $endpointInventoryInfo.Document)

    $resolvedLocalEndpointRoot = if ([string]::IsNullOrWhiteSpace($LocalEndpointRoot)) {
        Get-PackageDefaultLocalEndpointRoot
    }
    else {
        [string]$LocalEndpointRoot
    }

    if ([string]::Equals($EndpointMaterializationMode, 'endpointFocused', [System.StringComparison]::OrdinalIgnoreCase)) {
        $count = Sync-PackageEndpointCandidateDefinitions -SourceRows $sourceRows -TrustInventoryDocument $trustInventoryInfo.Document -ApplicationRootDirectory $ApplicationRootDirectory -LocalEndpointRoot $resolvedLocalEndpointRoot -CatalogTrustPolicy $CatalogTrustPolicy -CatalogTrustAllowUnsignedPublisherIds $CatalogTrustAllowUnsignedPublisherIds -CatalogTrustBlockedPublisherIds $CatalogTrustBlockedPublisherIds -UnknownSignedKeyPolicy $UnknownSignedKeyPolicy -DefinitionPublisherConflictMode $DefinitionPublisherConflictMode
        Write-PackageExecutionMessage -Message ("[STATE] Endpoint-wide definition materialization refreshed {0} Candidate definition file(s)." -f $count)
    }

    $candidates = @(Get-PackageDefinitionCandidateRows -SourceRows $sourceRows -ApplicationRootDirectory $ApplicationRootDirectory -TrustInventoryDocument $trustInventoryInfo.Document -DefinitionId $DefinitionId)
    if ($candidates.Count -eq 0) {
        $narrow = if ([string]::IsNullOrWhiteSpace($PublisherId)) { '' } else { " for publisher '$PublisherId'" }
        throw "Package definition '$DefinitionId' was not found in enabled endpoints$narrow."
    }

    $selected = Select-PackageDefinitionCandidateWinner -Candidates $candidates -DefinitionId $DefinitionId -PublisherId $PublisherId -CatalogTrustPolicy $CatalogTrustPolicy -CatalogTrustAllowUnsignedPublisherIds $CatalogTrustAllowUnsignedPublisherIds -CatalogTrustBlockedPublisherIds $CatalogTrustBlockedPublisherIds -UnknownSignedKeyPolicy $UnknownSignedKeyPolicy -DefinitionPublisherConflictMode $DefinitionPublisherConflictMode
    $selected = Resolve-PackageSelectedCandidateUnknownSigningKeyTrust -Candidate $selected -TrustInventoryDocumentInfo $trustInventoryInfo -UnknownSignedKeyPolicy $UnknownSignedKeyPolicy
    $candidateCopy = Copy-PackageDefinitionToLocalDefinitionStore -Role 'Candidate' -SourcePath $selected.DefinitionPath -LocalEndpointRoot $resolvedLocalEndpointRoot -PublisherId $selected.PublisherId -DefinitionId $selected.DefinitionId -DefinitionRevision $selected.DefinitionRevision
    $keyText = if ([string]::IsNullOrWhiteSpace([string]$selected.SignatureKeyThumbprint)) { '<none>' } else { [string]$selected.SignatureKeyThumbprint }
    $signerText = if ([string]::IsNullOrWhiteSpace([string]$selected.SignatureSignerDisplayName)) { '<none>' } else { [string]$selected.SignatureSignerDisplayName }
    Write-PackageExecutionMessage -Message ("[TRUST] Definition signature status='{0}' catalogTrust='{1}' key='{2}' signer='{3}' policy='{4}'." -f [string]$selected.SignatureStatus, [string]$selected.CatalogTrustStatus, $keyText, $signerText, [string]$CatalogTrustPolicy)

    return [pscustomobject]@{
        EndpointName                  = [string]$selected.EndpointName
        DefinitionId                  = [string]$selected.DefinitionId
        DefinitionPath                = [System.IO.Path]::GetFullPath($candidateCopy.Path)
        SourceKind                    = [string]$selected.EndpointSourceKind
        SourcePath                    = [string]$selected.DefinitionPath
        SourceDefinitionScanRootPath  = [string]$selected.DefinitionScanRootPath
        SourceHash                    = [string]$selected.SourceHash
        CandidatePath                 = [System.IO.Path]::GetFullPath($candidateCopy.Path)
        CandidateHash                 = [string]$candidateCopy.Hash
        SnapshotPath                  = $null
        SnapshotHash                  = $null
        ResolvedAtUtc                 = [DateTime]::UtcNow.ToString('o')
        SnapshotFallback              = $false
        EndpointInventoryPath         = $endpointInventoryInfo.Path
        TrustInventoryPath            = $trustInventoryInfo.Path
        Trusted                       = $true
        CatalogTrustPolicy            = [string]$CatalogTrustPolicy
        CatalogTrustAllowUnsignedPublisherIds = @($CatalogTrustAllowUnsignedPublisherIds)
        CatalogTrustBlockedPublisherIds = @($CatalogTrustBlockedPublisherIds)
        CatalogTrustStatus            = [string]$selected.CatalogTrustStatus
        CatalogTrustReason            = [string]$selected.CatalogTrustReason
        SignatureStatus               = [string]$selected.SignatureStatus
        SignatureValid                = [bool]$selected.SignatureValid
        SignatureTrusted              = [bool]$selected.SignatureTrusted
        SignatureKeyThumbprint        = if ([string]::IsNullOrWhiteSpace([string]$selected.SignatureKeyThumbprint)) { $null } else { [string]$selected.SignatureKeyThumbprint }
        SignatureSignerDisplayName    = if ([string]::IsNullOrWhiteSpace([string]$selected.SignatureSignerDisplayName)) { $null } else { [string]$selected.SignatureSignerDisplayName }
        SignatureCertificateSubject   = if ([string]::IsNullOrWhiteSpace([string]$selected.SignatureCertificateSubject)) { $null } else { [string]$selected.SignatureCertificateSubject }
        SignatureCertificatePem       = if ([string]::IsNullOrWhiteSpace([string]$selected.SignatureCertificatePem)) { $null } else { [string]$selected.SignatureCertificatePem }
        SignatureCertificateSource    = if ([string]::IsNullOrWhiteSpace([string]$selected.SignatureCertificateSource)) { $null } else { [string]$selected.SignatureCertificateSource }
        SignatureCertificateNotBeforeUtc = if ([string]::IsNullOrWhiteSpace([string]$selected.SignatureCertificateNotBeforeUtc)) { $null } else { [string]$selected.SignatureCertificateNotBeforeUtc }
        SignatureCertificateNotAfterUtc = if ([string]::IsNullOrWhiteSpace([string]$selected.SignatureCertificateNotAfterUtc)) { $null } else { [string]$selected.SignatureCertificateNotAfterUtc }
        SignatureCanonicalContentHash = if ([string]::IsNullOrWhiteSpace([string]$selected.SignatureCanonicalContentHash)) { $null } else { [string]$selected.SignatureCanonicalContentHash }
        PublisherId                   = [string]$selected.PublisherId
        PublisherName                 = [string]$selected.PublisherName
        DefinitionRevision            = [int]$selected.DefinitionRevision
        PublishedAtUtc                = [string]$selected.PublishedAtUtc
        MaterializationStatus         = [string]$candidateCopy.Status
        EndpointMaterializationMode   = [string]$EndpointMaterializationMode
    }
}