modules/Devolutions.CIEM.Checks/Private/Sync-CIEMCheckCatalog.ps1

function Sync-CIEMCheckCatalog {
    <#
    .SYNOPSIS
        Upserts provider check metadata from the checked-in catalog.

    .DESCRIPTION
        Validates the checked-in catalog for the requested provider, ensures every
        local check script is represented, and upserts catalog metadata into the
        SQLite checks table. Existing user enable/disable state is preserved.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateSet('Azure', 'AWS')]
        [string]$Provider
    )

    $checksRoot = switch ($Provider) {
        'Azure' { Join-Path $script:ModuleRoot 'modules/Azure/Checks' }
        'AWS' { Join-Path $script:ModuleRoot 'modules/AWS/Checks' }
        default { throw "Unsupported provider '$Provider'." }
    }

    $catalogPath = Join-Path $checksRoot 'check_catalog.json'
    if (-not (Test-Path $catalogPath)) {
        Write-Verbose "No check catalog found for provider '$Provider' at '$catalogPath'."
        return
    }

    $catalog = @(
        Get-Content -Path $catalogPath -Raw |
            ConvertFrom-Json -AsHashtable -ErrorAction Stop
    )
    if ($catalog.Count -eq 0) {
        throw "Check catalog '$catalogPath' is empty."
    }

    $catalogById = @{}
    $catalogByScript = @{}
    foreach ($entry in $catalog) {
        if ($entry.Provider -ne $Provider) {
            throw "Catalog entry '$($entry.Id)' declares provider '$($entry.Provider)' but is stored under '$Provider'."
        }

        if (-not $entry.Id) {
            throw "Catalog entry in '$catalogPath' is missing Id."
        }
        if (-not $entry.CheckScript) {
            throw "Catalog entry '$($entry.Id)' is missing CheckScript."
        }
        if ($catalogById.ContainsKey($entry.Id)) {
            throw "Duplicate check id '$($entry.Id)' in '$catalogPath'."
        }
        if ($catalogByScript.ContainsKey($entry.CheckScript)) {
            throw "Duplicate check script '$($entry.CheckScript)' in '$catalogPath'."
        }

        $catalogById[$entry.Id] = $entry
        $catalogByScript[$entry.CheckScript] = $entry
    }

    $scriptFiles = @(Get-ChildItem -Path $checksRoot -Filter '*.ps1' | Select-Object -ExpandProperty Name)
    $missingCatalogScripts = @($scriptFiles | Where-Object { -not $catalogByScript.ContainsKey($_) })
    if ($missingCatalogScripts.Count -gt 0) {
        throw "Check catalog '$catalogPath' is missing script entries: $($missingCatalogScripts -join ', ')"
    }

    $missingLocalScripts = @($catalogByScript.Keys | Where-Object { -not ($scriptFiles -contains $_) })
    if ($missingLocalScripts.Count -gt 0) {
        throw "Check catalog '$catalogPath' references missing scripts: $($missingLocalScripts -join ', ')"
    }

    $existingRows = @(Get-CIEMCheckMetadata -Provider $Provider)
    $existingById = @{}
    $existingByScript = @{}
    foreach ($row in $existingRows) {
        if ($existingById.ContainsKey($row.id)) {
            throw "Existing metadata contains duplicate id '$($row.id)'."
        }
        if ($existingByScript.ContainsKey($row.check_script)) {
            throw "Existing metadata contains duplicate script '$($row.check_script)'."
        }

        $existingById[$row.id] = $row
        $existingByScript[$row.check_script] = $row
    }

    foreach ($entry in $catalog) {
        $dataNeeds = if ($entry.ContainsKey('DataNeeds') -and $null -ne $entry.DataNeeds) {
            @($entry.DataNeeds)
        }
        else {
            $null
        }

        $catalogDisabled = [System.Convert]::ToBoolean($entry.Disabled)

        if (-not $catalogDisabled) {
            if ($null -eq $dataNeeds -or $dataNeeds.Count -eq 0) {
                throw "Enabled catalog entry '$($entry.Id)' must declare at least one data need."
            }

            foreach ($needKey in $dataNeeds) {
                if ($needKey -cne $needKey.ToLowerInvariant()) {
                    throw "Catalog entry '$($entry.Id)' declares non-canonical data need '$needKey'."
                }
            }
        }

        if ($existingById.ContainsKey($entry.Id)) {
            $existing = $existingById[$entry.Id]
            if ($existing.check_script -ne $entry.CheckScript) {
                throw "Existing metadata id '$($entry.Id)' points to '$($existing.check_script)', but catalog expects '$($entry.CheckScript)'."
            }
        }

        if ($existingByScript.ContainsKey($entry.CheckScript)) {
            $existing = $existingByScript[$entry.CheckScript]
            if ($existing.id -ne $entry.Id) {
                throw "Existing metadata script '$($entry.CheckScript)' uses id '$($existing.id)', but catalog expects '$($entry.Id)'."
            }
        }

        $disabled = if ($existingById.ContainsKey($entry.Id)) {
            [bool]$existingById[$entry.Id].disabled
        }
        else {
            $catalogDisabled
        }

        $dependsOn = if ($entry.ContainsKey('DependsOn') -and $null -ne $entry.DependsOn -and @($entry.DependsOn).Count -gt 0) {
            @($entry.DependsOn)
        }
        else {
            $null
        }

        $permissions = if ($entry.ContainsKey('Permissions') -and $null -ne $entry.Permissions) {
            $entry.Permissions | ConvertTo-Json -Compress -Depth 10
        }
        else {
            $null
        }

        Save-CIEMCheck -Id $entry.Id `
            -Provider $entry.Provider `
            -Service $entry.Service `
            -Title $entry.Title `
            -Description $entry.Description `
            -Risk $entry.Risk `
            -Severity $entry.Severity `
            -RemediationText $entry.Remediation.Text `
            -RemediationUrl $entry.Remediation.Url `
            -RelatedUrl $entry.RelatedUrl `
            -CheckScript $entry.CheckScript `
            -Disabled:$disabled `
            -Permissions $permissions `
            -DependsOn $dependsOn `
            -DataNeeds $dataNeeds
    }
}