Engines/Terraform/Invoke-AvmTerraformTest.ps1

function Invoke-AvmTerraformTest {
    <#
    .SYNOPSIS
        Validate Terraform examples and warn about uncovered local modules.

    .DESCRIPTION
        Initializes and validates each immediate examples/ configuration,
        including examples marked .e2eignore. Libraries remain child modules,
        allowing deprecated outputs. Diagnostics are relative to Context.Root.

        Each initialization uses a fresh, process-local TF_DATA_DIR so stale
        installed-module records cannot inflate coverage. Direct and transitive
        module directories are compared with the root and immediate modules/
        configurations. Uncovered modules produce warnings, not failures.

        FilesProcessed counts direct example configuration files. No examples
        returns Status='skipped'; real validation errors return Status='fail'.

    .PARAMETER Context
        Module context produced by Get-AvmModuleContext. Must have
        Ecosystem='terraform'.

    .PARAMETER AllowPathFallback
        Pass through to Resolve-AvmTool.

    .PARAMETER NoInit
        Validate examples using their existing initialization. Coverage is not
        assessed because existing module manifests can contain stale records;
        a warning reports that limitation.

    .OUTPUTS
        pscustomobject with Engine, Tool, ToolPath, ToolSource, Status,
        FilesProcessed, Issues.
    #>

    [CmdletBinding()]
    [OutputType([pscustomobject])]
    param(
        [Parameter(Mandatory)]
        $Context,

        [switch] $AllowPathFallback,

        [switch] $NoInit
    )

    Set-StrictMode -Version 3.0
    $ErrorActionPreference = 'Stop'

    if ($Context.Ecosystem -ne 'terraform') {
        throw [System.ArgumentException]::new(
            "Invoke-AvmTerraformTest requires a terraform context (got Ecosystem='$($Context.Ecosystem)').")
    }

    $tool = Resolve-AvmTool -Name 'terraform' -AllowPathFallback:$AllowPathFallback

    $scope = Get-AvmTerraformValidationScope -Root $Context.Root
    $issues = [System.Collections.Generic.List[object]]::new()
    $coverageIssues = [System.Collections.Generic.List[object]]::new()
    $coveredModules = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::Ordinal)
    $filesProcessed = 0
    $anyFail = $false

    foreach ($example in $scope.Examples) {
        $dataDirectory = $null
        $ownsDataDirectory = $false
        $environment = @{}
        try {
            if (-not $NoInit) {
                $dataDirectory = Join-Path ([System.IO.Path]::GetTempPath()) ('avm-validate-' + [guid]::NewGuid().ToString('N').Substring(0, 12))
                $null = New-Item -ItemType Directory -Path $dataDirectory -ErrorAction Stop
                $ownsDataDirectory = $true
                $environment.TF_DATA_DIR = $dataDirectory
                $initResult = Invoke-AvmProcess `
                    -FilePath $tool.Path `
                    -ArgumentList @('init', '-backend=false', '-upgrade', '-input=false', '-no-color') `
                    -WorkingDirectory $example.Path `
                    -EnvVars $environment `
                    -StreamOutput:(Test-AvmVerboseEnabled) `
                    -Label ('terraform init {0}' -f $example.RelativePath) `
                    -IgnoreExitCode

                if ($initResult.ExitCode -ne 0) {
                    $message = Add-AvmProcessFailureDetail `
                        -Message ("terraform init for '{0}' failed with exit code {1}." -f $example.RelativePath, $initResult.ExitCode) `
                        -StdOut $initResult.StdOut `
                        -StdErr $initResult.StdErr
                    throw [AvmProcessException]::new($message)
                }
            }

            $result = Invoke-AvmProcess `
                -FilePath $tool.Path `
                -ArgumentList @('validate', '-no-color', '-json') `
                -WorkingDirectory $example.Path `
                -EnvVars $environment `
                -IgnoreExitCode
            $filesProcessed += $example.Files.Count

            if ($result.ExitCode -notin @(0, 1)) {
                $message = Add-AvmProcessFailureDetail `
                    -Message ("terraform validate for '{0}' exited with code {1}." -f $example.RelativePath, $result.ExitCode) `
                    -StdOut $result.StdOut `
                    -StdErr $result.StdErr
                throw [AvmProcessException]::new($message)
            }

            try {
                $parsed = ConvertFrom-Json -InputObject ([string]$result.StdOut) -AsHashtable -ErrorAction Stop
            }
            catch [System.ArgumentException] {
                throw [AvmProcessException]::new(
                    "Could not parse terraform validate -json output for '$($example.RelativePath)': $($_.Exception.Message)")
            }
            if ($parsed -isnot [System.Collections.IDictionary] -or
                -not $parsed.Contains('valid') -or $parsed.valid -isnot [bool] -or
                -not $parsed.Contains('diagnostics') -or $parsed.diagnostics -isnot [array]) {
                throw [AvmProcessException]::new(
                    "Invalid terraform validate -json output for '$($example.RelativePath)': expected valid and diagnostics.")
            }

            $hasErrors = $false
            foreach ($diag in $parsed.diagnostics) {
                if ($diag -isnot [System.Collections.IDictionary] -or
                    -not $diag.Contains('severity') -or $diag.severity -notin @('error', 'warning') -or
                    -not $diag.Contains('summary') -or $diag.summary -isnot [string]) {
                    throw [AvmProcessException]::new(
                        "Invalid terraform validate diagnostic for '$($example.RelativePath)'.")
                }
                $severity = [string]$diag.severity
                if ($severity -eq 'error') { $hasErrors = $true }
                $detail = if ($diag.Contains('detail')) { [string]$diag.detail } else { '' }
                $message = if ($detail) { "$($diag.summary) - $detail" } else { [string]$diag.summary }
                $file = $example.RelativePath
                $line = 0
                $column = 0
                if ($diag.Contains('range') -and $diag.range) {
                    if ($diag.range.filename) {
                        $absoluteFile = [System.IO.Path]::GetFullPath([string]$diag.range.filename, $example.Path)
                        $file = [System.IO.Path]::GetRelativePath($Context.Root, $absoluteFile).Replace('\', '/')
                    }
                    if ($diag.range.start) {
                        $line = [int]$diag.range.start.line
                        $column = [int]$diag.range.start.column
                    }
                }
                $issues.Add([pscustomobject][ordered]@{
                        File     = $file
                        Line     = $line
                        Column   = $column
                        Severity = $severity
                        Code     = ''
                        Message  = ('[{0}] {1}' -f $example.RelativePath, $message)
                    })
            }

            if ($result.ExitCode -ne 0 -or -not $parsed.valid -or $hasErrors) {
                $anyFail = $true
                if (-not $hasErrors) {
                    $message = Add-AvmProcessFailureDetail `
                        -Message ("terraform validate for '{0}' reported failure without error diagnostics (exit {1})." -f $example.RelativePath, $result.ExitCode) `
                        -StdOut '' `
                        -StdErr $result.StdErr
                    $issues.Add([pscustomobject][ordered]@{
                            File     = $example.RelativePath
                            Line     = 0
                            Column   = 0
                            Severity = 'error'
                            Code     = ''
                            Message  = $message
                        })
                }
                continue
            }
            if ($NoInit) { continue }

            try {
                $manifestPath = Join-Path $dataDirectory 'modules' 'modules.json'
                $manifest = Get-Content -LiteralPath $manifestPath -Raw -ErrorAction Stop
                foreach ($directory in (ConvertFrom-AvmTerraformModuleManifest -Payload $manifest -WorkingDirectory $example.Path -TestFiles $example.TestFiles)) {
                    $null = $coveredModules.Add($directory)
                }
            }
            catch [System.Management.Automation.ItemNotFoundException], [System.IO.IOException], [System.UnauthorizedAccessException], [AvmConfigurationException] {
                $coverageIssues.Add([pscustomobject][ordered]@{
                        File     = $example.RelativePath
                        Line     = 0
                        Column   = 0
                        Severity = 'warning'
                        Code     = 'terraform.module-coverage-unavailable'
                        Message  = "Could not determine module coverage for '$($example.RelativePath)': $($_.Exception.Message)"
                    })
            }
        }
        finally {
            if ($ownsDataDirectory -and (Test-Path -LiteralPath $dataDirectory -PathType Container)) {
                Remove-Item -LiteralPath $dataDirectory -Recurse -Force -ErrorAction Stop -ProgressAction SilentlyContinue
            }
        }
    }

    if ($scope.Examples.Count -eq 0) {
        $coverageIssues.Add([pscustomobject][ordered]@{
                File     = ''
                Line     = 0
                Column   = 0
                Severity = 'warning'
                Code     = 'terraform.module-coverage'
                Message  = 'No Terraform examples found under examples/; validation was skipped.'
            })
    }
    if ($NoInit -and $scope.Examples.Count -gt 0) {
        $coverageIssues.Add([pscustomobject][ordered]@{
                File     = ''
                Line     = 0
                Column   = 0
                Severity = 'warning'
                Code     = 'terraform.module-coverage-unavailable'
                Message  = 'Module coverage is not assessed with -NoInit because existing module manifests may be stale. Rerun without -NoInit to check coverage.'
            })
    }
    else {
        foreach ($module in $scope.Modules) {
            if ($coveredModules.Contains($module.Path)) { continue }
            $coverageIssues.Add([pscustomobject][ordered]@{
                    File     = [System.IO.Path]::GetRelativePath($Context.Root, $module.Files[0]).Replace('\', '/')
                    Line     = 0
                    Column   = 0
                    Severity = 'warning'
                    Code     = 'terraform.module-coverage'
                    Message  = "No example coverage was confirmed for module '$($module.RelativePath)'. Add an example that calls this checkout's module directly or transitively."
                })
        }
    }
    foreach ($issue in $coverageIssues) {
        $issues.Add($issue)
        Write-AvmLog -Message $issue.Message -Level Warning -File $issue.File
        Register-AvmPresentedIssue -Issue $issue
    }

    return [pscustomobject][ordered]@{
        Engine         = 'terraform'
        Tool           = ('{0}/{1}' -f $tool.Name, $tool.Version)
        ToolPath       = $tool.Path
        ToolSource     = $tool.Source
        Status         = if ($anyFail) { 'fail' } elseif ($scope.Examples.Count -eq 0) { 'skipped' } else { 'pass' }
        FilesProcessed = $filesProcessed
        Issues         = $issues.ToArray()
    }
}

# SIG # Begin signature block
# MIInUAYJKoZIhvcNAQcCoIInQTCCJz0CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBJFEbWSv4UGoia
# 29WDed0s1s1LkCXh8icjnCVe1YzwKaCCDMkwggYEMIID7KADAgECAhMzAAACHPrN
# xZvoL37EAAAAAAIcMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNVBAYTAlVTMR4wHAYD
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBD
# b2RlIFNpZ25pbmcgUENBIDIwMjQwHhcNMjYwNDE2MTg1OTQxWhcNMjcwNDE1MTg1
# OTQxWjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYD
# VQQDExVNaWNyb3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IB
# DwAwggEKAoIBAQDVsZfgOKmM31HPfoWOoNEiw0SlCiIxUMC0I9NMWbucKOw/e9lP
# oAoehQVu6SG65V4EPzrYsnBnFPNoi4/HoOdjhz1qkrEt4I6tEcxXU6oOeY9zGveC
# /3iBeuhLYxM3M/PkcUoebF+Nednm8OkdSPoDu8imViHPQq/8CQUu0WRR4rE+dMRf
# rpVqfmNi2qWCX94T4MsepijGVkwE//tJg0ryAiYdHT34LSnlG/RSBZmQRGWZ5g8j
# qnKjRParSqMft1gvjuUTVgtWNZfgcLFSK5Wa0myrq8OPcgTGGsRgun+tnSS+IxDT
# xVsAPH1OzvPjwomguByhUe/OcvUN0D5Wmp7xAgMBAAGjggGqMIIBpjAOBgNVHQ8B
# Af8EBAMCB4AwHwYDVR0lBBgwFgYKKwYBBAGCN0wIAQYIKwYBBQUHAwMwHQYDVR0O
# BBYEFNoH7a2YDjOSwpkp6DHcmUS7J+0yMFQGA1UdEQRNMEukSTBHMS0wKwYDVQQL
# EyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxFjAUBgNVBAUT
# DTIzMDAxMis1MDc1NjkwHwYDVR0jBBgwFoAUf1k/VCHarU/vBeXmo9ctBpQSCDEw
# YAYDVR0fBFkwVzBVoFOgUYZPaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9w
# cy9jcmwvTWljcm9zb2Z0JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNy
# bDBtBggrBgEFBQcBAQRhMF8wXQYIKwYBBQUHMAKGUWh0dHA6Ly93d3cubWljcm9z
# b2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwQ29kZSUyMFNpZ25pbmcl
# MjBQQ0ElMjAyMDI0LmNydDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IC
# AQAUnEqhaRXe0T3hIJjvdQErEkrA/7bByjn6t5IArODkkRjzkYwtKMc2yYj2quaN
# rLutWw2YZcngKPy1b71YyDJQTy4NDRwaSh9Tw5thrk3NmcPrAHia5vtcBJ1CgtKK
# 7mQbIcQ22d/N3813ayCDDFewu1+jsZmX+r/aTEqaOM4TVxVtRSkuCy8nAXKuChOK
# Li/zA4XuH8iEYqIsj2YoNaeSxVmeGiERXpKdo3dDmYi0kO5w2D8VS4c3+9h6gElY
# BaAAg/dYErBg27qT3vv0zRDJhJufvCNylA8S7/+8H5E/PV5cng6na9VV/w9OV3qu
# uND6zdGa2EX38Glp50F9AIQk3p2xXmcvorDeM4XJ7UlWYBi6g80J1SSOQnInCYFE
# msfUNn3+1AaTJKSJL83quKArTac2pKhu0Yzzzrzo6HrsRiQKzpnRBb1/dMa6P3hz
# 75XbMRBctNsFhZC07WCmjExdLg2eHW5uV0TY8D5+6wozJf7vF3+WHkYPO85Z+BC6
# U4FkNbYNycZ9cE4j1tXRdyDCfml6c0HWPHjNVDObrv9lKt3qUqFpX38VCqVCyNOO
# 1UcXfQiVjJw32U2WUKZjt/neJKHEBsm9kFsLuWzkQ53+qcaSaytmsCnk2gOglrlD
# 5d3kKyvvAw+rzm0lT8K38P6PLxfZQHhu4W8dV7Av8N2ZmDCCBr0wggSloAMCAQIC
# EzMAAAA5O7Y3Gb8GHWcAAAAAADkwDQYJKoZIhvcNAQEMBQAwgYgxCzAJBgNVBAYT
# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBS
# b290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDExMB4XDTI0MDgwODIwNTQxOFoX
# DTM2MDMyMjIyMTMwNFowVzELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFU1pY3Jvc29m
# dCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQ
# Q0EgMjAyNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANgBnB7jOMeq
# lRYHNa265v4IY9fH8TKhemHfPINe1gpLaV3dhg324WwH06LcHbpnsBukCDNitryo
# 0dtS/EW6I/yEL/bLSY8hKpbfQuWusBPr9qazYcDxCW/qnjb5JsI1s8bNOg3bVATv
# QVL4tcf03aTycsz8QeCdM0l/yHRObJ9QqazM1r6VPEOJ7LL+uEEb73w6QCuhs89a
# 1uv1zerOYMnsneRRwCbpyW11IcggU0cRKDDq1pjVJzIbIF6+oiXXbReOsgeI8zu1
# FyQfK0fVkaya8SmVHQ/tOf23mZ4W9k0Ri22QW9p3UgSC5OUDktKxxcCmGL6tXLfO
# GSWHIIV4YrTJTT6PNty5REojHJuZHArkF9VnHTERWoTjAzfI3kP+5b4alUdhgAZ7
# ttOu1bVnXfHaqPYl2rPs20ji03LOVWsh/radgE17es5hL+t6lV0eVHrVhsssROWJ
# uz2MXMCt7iw7lFPG9LXKGjsmonn2gotGdHIuEg5JnJMJVmixd5LRlkmgYRZKzhxS
# CwyoGIq0PhaA7Y+VPct5pCHkijcIIDm0nlkK+0KyepolcqGm0T/GYQRMhHJlGOOm
# VQop36wUVUYklUy++vDWeEgEo4s7hxN6mIbf2MSIQ/iIfMZgJxC69oukMUXCrOC3
# SkE/xIkgpfl22MM1itkZ35nNXkMolU1lAgMBAAGjggFOMIIBSjAOBgNVHQ8BAf8E
# BAMCAYYwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFH9ZP1Qh2q1P7wXl5qPX
# LQaUEggxMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMA8GA1UdEwEB/wQFMAMB
# Af8wHwYDVR0jBBgwFoAUci06AjGQQ7kUBU7h6qfHMdEjiTQwWgYDVR0fBFMwUTBP
# oE2gS4ZJaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMv
# TWljUm9vQ2VyQXV0MjAxMV8yMDExXzAzXzIyLmNybDBeBggrBgEFBQcBAQRSMFAw
# TgYIKwYBBQUHMAKGQmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMv
# TWljUm9vQ2VyQXV0MjAxMV8yMDExXzAzXzIyLmNydDANBgkqhkiG9w0BAQwFAAOC
# AgEAFJQfOChP7onn6fLIMKrSlN1WYKwDFgAddymOUO3FrM8d7B/W/iQ6DxXsDn7D
# 5W4wMwYeLystcEqfkjz4NURRgazyMu5yRzQh4LqjA4tStTcJh1opExo7nn5PuPBY
# nbu0+THSuVHTe0VTTPVhily/piFrDo3axQ9P4C+Ol5yet+2gTfekICS5xS+cYfSI
# vgn0JksVBVMYVI5QFu/qhnLhsEFEUzG8fvv0hjgkO+lkpV9ty6GkN4vdnd7ya6Q6
# aR9y34aiM1qmxaxBi6OUnyNl6fkuun/diTFnYDLTppOkr/mg5WSfCiDVMNCxtj4w
# PKC5OmHm1DQIt/MNokbbH3UGsFP1QbzsLocuSqLCvH09Io3fDPTmscR9Y75G4qX7
# RTX8AdBPo0I6OEojf39zuFZt0qOHm65YWQE69cZM2ueE1MB05dNNgHK9gTE7zKvK
# /fg8B2qjW88MT/WF5V5uvZGtqa9FSL2RazArA+rDPuf6JGYz4HpgMZHB4S6szWSK
# YBv0VisCzfxgeU+dquXW9bd0auYlOB58DPcOYKdc3Se94g+xL4pcEhbB54JOgAkw
# YTu/9dLeH2pDqeJZAABVDWRQCaXfO5LgyKwKCLYXpigrZYCjUSBcr+Ve8PFWMhVT
# Ql0v4q8J/AUmQN5W4n101cY2L4A7GTQG1h32HHAvfQESWP0xghndMIIZ2QIBATBu
# MFcxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
# KDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMjQCEzMAAAIc
# +s3Fm+gvfsQAAAAAAhwwDQYJYIZIAWUDBAIBBQCggZAwGQYJKoZIhvcNAQkDMQwG
# CisGAQQBgjcCAQQwLwYJKoZIhvcNAQkEMSIEIC/DCbHK+EjDMIrGhCzNfZ/cXQR7
# sEsMMuP0TTx5sKpAMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8AcwBv
# AGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEBBQAE
# ggEATfZi8rYXiZDFvNZUE5ywCFerdBnXwHCE7AzuSlP2OySYDdZVbpPp8fxq0UpF
# 6D18vzfPPtLe5soxZFMg54KQrbCIzYM1aNq3cwEwTYYxn7Xycw+E74f22ly4uUQB
# zoBCz6sJeVncNd3g3LU3NJbLn3KSdMENqRprlYzX0p4xswFL8qrpiLpL8Mzssf7L
# b4Nn4z1PSFVpqbtx+7dl44gKrNNZp0/VcfxeOJc+6Sv7NrLFG6uQLsUZNNnRF5lj
# v2GqOcdYNVvqAjNyjhAHr503t9O2GdyaCtiI/pvD+ockDnNIoIxbMYRnTLOSySvQ
# 3TxQ2m1VLwFFF/mqSAr28Mhq3qGCF60wghepBgorBgEEAYI3AwMBMYIXmTCCF5UG
# CSqGSIb3DQEHAqCCF4YwgheCAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFaBgsqhkiG
# 9w0BCRABBKCCAUkEggFFMIIBQQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFlAwQC
# AQUABCBSY6u6akwhC0Q81qVQUOX4z6q2y+k8MUio7cEZzfez6AIGaohsS4NjGBMy
# MDI2MDkxNjE3NDUwNC41NDRaMASAAgH0oIHZpIHWMIHTMQswCQYDVQQGEwJVUzET
# MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV
# TWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFu
# ZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjoz
# MjFBLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy
# dmljZaCCEfswggcoMIIFEKADAgECAhMzAAACGqmgHQagD0OqAAEAAAIaMA0GCSqG
# SIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
# JjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTI1MDgx
# NDE4NDgyOFoXDTI2MTExMzE4NDgyOFowgdMxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
# EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv
# ZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh
# dGlvbnMgTGltaXRlZDEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjMyMUEtMDVF
# MC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIIC
# IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAmYEAwSTz79q2V3ZWzQ5Ev7RK
# gadQtMBy7+V3XQ8R0NL8R9mupxcqJQ/KPeZGJTER+9Qq/t7HOQfBbDy6e0TepvBF
# V/RY3w+LOPMKn0Uoh2/8IvdSbJ8qAWRVoz2S9VrJzZpB8/f5rQcRETgX/t8N66D2
# JlEXv4fZQB7XzcJMXr1puhuXbOt9RYEyN1Q3Z7YjRkhfBsRc+SD/C9F4iwZqfQgo
# 82GG4wguIhjJU7+XMfrv4vxAFNVg3mn1PoMWGZWio+e14+PGYPVLKlad+0IhdHK5
# AgPyXKkqAhEZpYhYYVEItHOOvqrwukxVAJXMvWA3GatWkRZn33WDJVtghCW6XPLi
# 1cDKiGE5UcXZSV4OjQIUB8vp2LUMRXud5I49FIBcE9nT00z8A+EekrPM+OAk07aD
# fwZbdmZ56j7ub5fNDLf8yIb8QxZ8Mr4RwWy/czBuV5rkWQQ+msjJ5AKtYZxJdnaZ
# ehUgUNArU/u36SH1eXKMQGRXr/xeKFGI8vvv5Jl1knZ8UqEQr9PxDbis7OXp2WSM
# K5lLGdYVH8VownYF3sbOiRkx5Q5GaEyTehOQp2SfdbsJZlg0SXmHphGnoW1/gQ/5
# P6BgSq4PAWIZaDJj6AvLLCdbURgR5apNQQed2zYUgUbjACA/TomA8Ll7Arrv2oZG
# iUO5Vdi4xxtA3BRTQTUCAwEAAaOCAUkwggFFMB0GA1UdDgQWBBTwqyIJ3QMoPasD
# cGdGovbaY8IlNjAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBfBgNV
# HR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2Ny
# bC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmwwbAYI
# KwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAy
# MDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMI
# MA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAgEA1a72WFq7B6bJT3VO
# J21nnToPJ9O/q51bw1bhPfQy67uy+f8x8akipzNL2k5b6mtxuPbZGpBqpBKguDwQ
# mxVpX8cGmafeo3wGr4a8Yk6Sy09tEh/Nwwlsyq7BRrJNn6bGOB8iG4OTy+pmMUh7
# FejNPRgvgeo/OPytm4NNrMMg98UVlrZxGNOYsifpRJFg5jE/Yu6lqFa1lTm9cHuP
# YxWa2oEwC0sEAsTFb69iKpN0sO19xBZCr0h5ClU9Pgo6ekiJb7QJoDzrDoPQHwbN
# A87Cto7TLuphj0m9l/I70gLjEq53SHjuURzwpmNxdm18Qg+rlkaMC6Y2KukOfJ7o
# CSu9vcNGQM+inl9gsNgirZ6yJk9VsXEsoTtoR7fMNU6Py6ufJQGMTmq6ZCq2eIGO
# XWMBb79ZF6tiKTa4qami3US0mTY41J129XmAglVy+ujSZkHu2lHJDRHs7FjnIXZV
# UE5pl6yUIl23jG50fRTLQcStdwY/LvJUgEHCIzjvlLTqLt6JVR5bcs5aN4Dh0YPG
# 95B9iDMZrq4rli5SnGNWev5LLsDY1fbrK6uVpD+psvSLsNpht27QcHRsYdAMALXM
# +HNsz2LZ8xiOfwt6rOsVWXoiHV86/TeMy5TZFUl7qB59INoMSJgDRladVXeT9fwO
# uirFIoqgjKGk3vO2bELrYMN0QVwwggdxMIIFWaADAgECAhMzAAAAFcXna54Cm0mZ
# AAAAAAAVMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0
# ZSBBdXRob3JpdHkgMjAxMDAeFw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMyMjVa
# MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT
# HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0BAQEF
# AAOCAg8AMIICCgKCAgEA5OGmTOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51yMo1
# V/YBf2xK4OK9uT4XYDP/XE/HZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY6GB9
# alKDRLemjkZrBxTzxXb1hlDcwUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9cmmv
# Haus9ja+NSZk2pg7uhp7M62AW36MEBydUv626GIl3GoPz130/o5Tz9bshVZN7928
# jaTjkY+yOSxRnOlwaQ3KNi1wjjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDuaRr3t
# pK56KTesy+uDRedGbsoy1cCGMFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74kpEe
# HT39IM9zfUGaRnXNxF803RKJ1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2K26o
# ElHovwUDo9Fzpk03dJQcNIIP8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5TI4C
# vEJoLhDqhFFG4tG9ahhaYQFzymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZki1ug
# poMhXV8wdJGUlNi5UPkLiWHzNgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9QBXps
# xREdcu+N+VLEhReTwDwV2xo3xwgVGD94q0W29R6HXtqPnhZyacaue7e3PmriLq0C
# AwEAAaOCAd0wggHZMBIGCSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUCBBYE
# FCqnUv5kxJq+gpE8RjUpzxD/LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJlpxtT
# NRnpcjBcBgNVHSAEVTBTMFEGDCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIBFjNo
# dHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9yeS5o
# dG0wEwYDVR0lBAwwCgYIKwYBBQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBD
# AEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZW
# y4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5t
# aWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAt
# MDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0y
# My5jcnQwDQYJKoZIhvcNAQELBQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/ypb+pc
# FLY+TkdkeLEGk5c9MTO1OdfCcTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulmZzpT
# Td2YurYeeNg2LpypglYAA7AFvonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM9W0j
# VOR4U3UkV7ndn/OOPcbzaN9l9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECWOKz3
# +SmJw7wXsFSFQrP8DJ6LGYnn8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4FOmR
# sqlb30mjdAy87JGA0j3mSj5mO0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3UwxTSw
# ethQ/gpY3UA8x1RtnWN0SCyxTkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPXfx5b
# RAGOWhmRaw2fpCjcZxkoJLo4S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVXVAmx
# aQFEfnyhYWxz/gq77EFmPWn9y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGConsX
# HRWJjXD+57XQKBqJC4822rpM+Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU5nR0
# W2rRnj7tfqAxM328y+l7vzhwRNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEGahC0
# HVUzWLOhcGbyoYIDVjCCAj4CAQEwggEBoYHZpIHWMIHTMQswCQYDVQQGEwJVUzET
# MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV
# TWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFu
# ZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjoz
# MjFBLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy
# dmljZaIjCgEBMAcGBSsOAwIaAxUA8YrutmKpSrubCaAYsU4pt1Ft8DaggYMwgYCk
# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQsFAAIF
# AO5VLwMwIhgPMjAyNjA5MTYxNTA2NDNaGA8yMDI2MDkxNzE1MDY0M1owdDA6Bgor
# BgEEAYRZCgQBMSwwKjAKAgUA7lUvAwIBADAHAgEAAgIHaTAHAgEAAgISZTAKAgUA
# 7laAgwIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAID
# B6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBCwUAA4IBAQCgJu3EhdDTz6E8jUY/
# kwqjO7iCH1x/OHOw1QNwCeWc533n3ydbYGH7ndFchGjJpYl+m4x0pL9ArkHfySqS
# /CrPEW1mOPnFdI+k4Rt4IyYaCBQXyZ3RpwE+RVViNSvPonWVajwHAB2b4q1Z0LGu
# ycMubuEP3fnTrr5THztU82trb43LGizJaQGERh4zH+zM3ii1MG6Z8k5dKOtURQBc
# +1SsrjYPAdjSVkb+AemQMnu2VXfOs1br6kd1FZ7fsCVdI5G6cwSl3iK3zF/HphRs
# d9UDI41FacWC4E5JNORXdg5cU2KeoJU9QUcPT4bDQ9thyofH6u17x4N6qPTkn/7u
# 0DL4MYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAC
# EzMAAAIaqaAdBqAPQ6oAAQAAAhowDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3
# DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgWkBug4k1SVaENwWs
# yXkpf00Gew/7edLfc18cBkK8bGIwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9
# BCCdeiHHrbtpKcwB20doVU89WHIOH8S7w37uaHcDmemK+zCBmDCBgKR+MHwxCzAJ
# BgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25k
# MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jv
# c29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAACGqmgHQagD0OqAAEAAAIaMCIE
# ICPZyp/VBagmp+sGM1vRCFHrU/7umHFPwlePNcFSxsp2MA0GCSqGSIb3DQEBCwUA
# BIICABc0RQaLvkmHn5r1lsVD9ifrto7GtlaGFTGhK/QrgoKNGX9nYr7k77HOxvnb
# FeQAd8i/eji30hK+CPouOdJqDtwXz9F7ToYz/C6qI9tzO+WUwSL3lK7MRqgO2/pN
# i2hGO5GIWZOlTvzHrU4GGOrSrMKfdSuk4P/EMA0l8UJZ4SIjm39f3ZDprvWjlHXb
# qa4Gsx5SqjocaFbQ+o+iimrzBaoXduXB7roxcfm0lPv3R7walgLctwOktvHSxcSf
# MXIds1JwF/DcEMZ9jjgiZTbLPLwYcFCxzeNa/QpjYWGDWvKAqQWuNl74GMiorkqM
# 7Ehvw1Fdk7oRiWVZKmdbTJ/NC58nVpxkq7mCRsLH5Frn1Eu73TLCc3TGxTqfcX42
# jV/W2xfTsWVh2Psb7+glzzgVysejfP7U6tfKfzoH6A1+bdxvdBAYO+V+tG5jC7zw
# S+56PnehmGjcePXc6Qovw436v1GNPBthWG0naLJMQT1L9bKfb6XLwtujtUD8NHe6
# joGtkoyJeR1W7FmWUzzsB/qeiCszfZvVHR3ngnRYwMNeTglpsXkrFNpY4EzDnZ8J
# Zt8LNwOR14Jf9zS0fW8grWq3CtTnZgH7f13RckpgP0oN/qWP3KjHiHh4NGqiYryC
# TAlpoQsmyrUKDddcnbmXN2DZrA0e2kvPsME8TIOqJM+AJbLH
# SIG # End signature block