Public/Invoke-AvmPrCheck.ps1
|
function Invoke-AvmPrCheck { <# .SYNOPSIS Run the pull-request linting and drift gauntlet against the resolved module: metadata -> initialize -> sync -> format -> transform -> lint -> check policy -> check convention -> validate -> docs. .DESCRIPTION Composition cmdlet. Resolves the module context once with Get-AvmModuleContext, then invokes the verb chain above in sequence against that same module root. Each step's structured result is captured. The overall Status is 'pass' only when every executed step reports Status='pass' (or didn't throw, for verbs that don't carry a Status field). This is the broader sibling of Invoke-AvmPreCommit. It adds the credentialled policy evaluation and read-only drift checks used to verify that pre-commit output is current. Before any step runs, git status must report a clean working tree. Metadata validation runs after tool resolution but before the other steps. Missing or invalid root or child metadata aborts the chain without changing module files or reading indexes, regardless of StopOnFail, unless metadata is explicitly excluded with -ExcludeSteps. ExcludeSteps omits named steps and tools needed only by those steps. Excluded steps remain visible as 'skipped' in the result and log. Excluding every step returns overall Status='skipped', not 'pass'. For Terraform, the initialize step prepares each source example once. The later validate step reuses that persistent `.terraform` state instead of initializing the example again. The 'validate' step is a build-validation pass ('terraform validate' / 'bicep build'), not a test run. Unit tests remain a separate CI job so a failure produces one actionable signal and fork contributors receive results without environment approval. The convention step requires a tests/unit/*.tftest.hcl fixture, preventing an empty unit tier from reading as a green gauntlet. After metadata validation, the managed-files sync step (terraform only) in **drift-check mode** (-CheckDrift): unlike pre-commit, which reconciles the governed files by writing them, pr-check writes nothing and instead treats any needed add/update/remove as Status='fail'. This makes stale governed files a hard CI failure so the module is refreshed before merge rather than silently rewritten in CI. For Bicep the sync step is unsupported and skipped. Bicep policy runs the required and advisory PSRule baselines when its repository config and selected test sources are available; missing inputs fail the check. Bicep policy, convention, and docs must report inspectable results rather than skipping. The required docs step checks README drift and confirms every selected source-backed README was rendered; source-less READMEs are reported separately. The sync step also gates on the managed-files release recorded in '.avm/managed-files-version.json': governed files are compared against that pinned release, and a superseded major version is reported as a drift issue so the run fails until the repository adopts it with 'avm pre-commit -Upgrade'. Status semantics (same as Invoke-AvmPreCommit): - 'pass' : step returned Status='pass' (or didn't throw for format). - 'fail' : step returned Status='fail'. - 'error' : step threw an unexpected exception; the chain aborts. - 'skipped' : step was explicitly excluded or threw AvmNotSupportedException because it does not apply to the selected ecosystem. - configuration exceptions are failures, not skips. By default the gauntlet is fail-soft: a step that returns Status='fail' does NOT abort subsequent steps - the caller gets the full picture in one run. A step that THROWS (non- AvmConfigurationException) IS fatal and aborts the rest of the chain. Set -StopOnFail to abort on the first Status='fail' instead. Routed by the dispatcher: 'avm pr-check'. .PARAMETER Path Working directory whose enclosing module to validate. Defaults to the current location. .PARAMETER Ecosystem Force the ecosystem selector. Defaults to 'auto'. .PARAMETER AllowPathFallback When set, accept a PATH-resolved tool binary that self-reports the lock-pinned version. Forwarded to each step. .PARAMETER StopOnFail When set, abort the chain on the first step whose Status is 'fail'. A throwing step is always fatal regardless of this flag. .PARAMETER ExcludeSteps Step names to skip: metadata, sync, format, transform, lint, check policy, check convention, validate, docs. Accepts an array; names are case-insensitive and duplicates are ignored. Unknown or empty names are rejected. Omit this parameter or supply an empty array to run every step. Version and clean-working-tree checks always run. .PARAMETER ThrottleLimit Maximum number of independent Terraform transform targets, lint scopes, or policy examples to process at once. Defaults to four. .PARAMETER SkipModuleVersionCheck Skip the PowerShell Gallery check that otherwise stops the command when a newer Avm.Authoring version is available. Writes a warning once. .OUTPUTS pscustomobject with: - Path : the resolved module root - Ecosystem : bicep | terraform - Status : pass | fail | error | skipped - Steps : array of { Step, Status, Error?, Result?, DurationMs } - DurationMs : total wall-clock cost .EXAMPLE avm pr-check .EXAMPLE Invoke-AvmPrCheck -Path C:\repos\my-module -StopOnFail .EXAMPLE avm pr-check -ExcludeSteps @('check policy', 'docs') .EXAMPLE avm pr-check -Ecosystem terraform -ExcludeSteps 'check policy' Run the fork-compatible Terraform checks. Run unit tests separately with 'avm test unit'. .NOTES The reusable Terraform workflow runs fork pr-check and unit tests in independent jobs. Fork pr-check excludes only check policy and its Conftest prerequisite. Both jobs omit GitHub environments, subscription selection, Azure OIDC permissions, and secret/variable preparation. Inherited secret and variable payloads are replaced with empty objects. Only GitHub's automatically provided read-only token remains available; no configured secrets are required. Unit fixtures and repository hooks must work without Azure credentials. Normal branch jobs retain their full checks, credentials, environments, and integration/end-to-end tests. Publish a compatible Avm.Authoring release containing -ExcludeSteps before adopting this workflow revision. Older releases fail with upgrade guidance rather than running policy or silently omitting the other checks. #> [CmdletBinding()] [OutputType([pscustomobject])] param( [Parameter(Position = 0)] [string] $Path = $PWD.Path, [ValidateSet('auto', 'bicep', 'terraform')] [string] $Ecosystem = 'auto', [switch] $AllowPathFallback, [switch] $StopOnFail, [ValidateNotNull()] [ValidateSet('metadata', 'sync', 'format', 'transform', 'lint', 'check policy', 'check convention', 'validate', 'docs')] [string[]] $ExcludeSteps = @(), [ValidateRange(1, 32)] [int] $ThrottleLimit = 4, [switch] $SkipModuleVersionCheck ) Set-StrictMode -Version 3.0 $ErrorActionPreference = 'Stop' Test-AvmModuleVersion -SkipModuleVersionCheck:$SkipModuleVersionCheck $startTime = [datetime]::UtcNow $sw = [System.Diagnostics.Stopwatch]::StartNew() $context = Get-AvmModuleContextInternal -Path $Path -Ecosystem $Ecosystem Write-AvmLog ("pr-check: module root = {0}; ecosystem = {1}" -f $context.Root, $context.Ecosystem) -Level Verbose | Out-Null Assert-AvmGitWorkingTreeClean -Path $context.Root $null = Resolve-AvmCommandTool -Command 'pr-check' -Ecosystem $context.Ecosystem -ModuleRoot $context.Root -AllowPathFallback:$AllowPathFallback -ExcludeSteps $ExcludeSteps $stepDefs = @( [pscustomobject]@{ Name = 'metadata' Cmdlet = 'Test-AvmMetadataModules' ContextOnly = $true ExtraArgs = @{ Context = $context } } if ($context.Ecosystem -eq 'terraform') { [pscustomobject]@{ Name = 'initialize' Cmdlet = 'Initialize-AvmTerraformCommand' ContextOnly = $true ExtraArgs = @{ Context = $context Command = 'pr-check' AllowPathFallback = $AllowPathFallback } } } [pscustomobject]@{ Name = 'sync'; Cmdlet = 'Invoke-AvmSync'; ExtraArgs = @{ CheckDrift = $true } } [pscustomobject]@{ Name = 'format'; Cmdlet = 'Invoke-AvmFormat'; ExtraArgs = @{ CheckDrift = $true } } [pscustomobject]@{ Name = 'transform'; Cmdlet = 'Invoke-AvmTransform' ExtraArgs = @{ CheckDrift = $true; ThrottleLimit = $ThrottleLimit } } [pscustomobject]@{ Name = 'lint'; Cmdlet = 'Invoke-AvmLint' ExtraArgs = @{ ThrottleLimit = $ThrottleLimit } } [pscustomobject]@{ Name = 'check policy'; Cmdlet = 'Invoke-AvmCheckPolicy' ExtraArgs = @{ ThrottleLimit = $ThrottleLimit } } [pscustomobject]@{ Name = 'check convention'; Cmdlet = 'Invoke-AvmCheckConvention' } if ($context.Ecosystem -eq 'terraform') { [pscustomobject]@{ Name = 'validate'; Cmdlet = 'Invoke-AvmTest' ExtraArgs = @{ UseExistingInit = $true } } } else { [pscustomobject]@{ Name = 'validate'; Cmdlet = 'Invoke-AvmTest' } } [pscustomobject]@{ Name = 'docs'; Cmdlet = 'Invoke-AvmDocs'; ExtraArgs = @{ CheckDrift = $true } } ) $steps = New-Object System.Collections.Generic.List[object] $overall = if (@($stepDefs | Where-Object { $_.Name -notin $ExcludeSteps }).Count -eq 0) { 'skipped' } else { 'pass' } $stepIndex = 0 foreach ($def in $stepDefs) { $stepStatus = 'pass' $stepError = $null $stepResult = $null $requiredBicepStep = $context.Ecosystem -eq 'bicep' -and $def.Name -in @('check policy', 'check convention', 'docs') $stepIndex++ $stepStart = [datetime]::UtcNow if ($def.Name -in $ExcludeSteps) { Write-AvmLog ('step {0}/{1}: {2} -> skipped (excluded by -ExcludeSteps)' -f $stepIndex, $stepDefs.Count, $def.Name) -Level Info | Out-Null $steps.Add([pscustomobject][ordered]@{ Step = $def.Name Status = 'skipped' Error = 'Excluded by -ExcludeSteps.' Result = $null StartTime = $stepStart EndTime = $stepStart DurationMs = 0 }) continue } $stepSw = [System.Diagnostics.Stopwatch]::StartNew() Write-AvmLog ('step {0}/{1}: {2} (started {3})' -f $stepIndex, $stepDefs.Count, $def.Name, (Format-AvmTimestamp -Timestamp $stepStart)) -Level Info | Out-Null try { $extraArgs = if ($def.PSObject.Properties.Name -contains 'ExtraArgs' -and $def.ExtraArgs) { $def.ExtraArgs } else { @{} } $stepParameters = @{} if (-not $def.PSObject.Properties['ContextOnly'] -or -not $def.ContextOnly) { $stepParameters = @{ Path = $context.Root Ecosystem = $context.Ecosystem AllowPathFallback = $AllowPathFallback SkipModuleVersionCheck = $SkipModuleVersionCheck } } $stepResult = Invoke-AvmNestedCommand { & $def.Cmdlet @stepParameters @extraArgs } $hasStatus = $stepResult -and $stepResult.PSObject.Properties.Name -contains 'Status' if ($hasStatus) { $stepStatus = $stepResult.Status } if ($requiredBicepStep) { if (-not $hasStatus) { $stepStatus = 'fail' $stepError = "Required Bicep $($def.Name) returned no status; keep the registry static-validation jobs until this check runs." } elseif ($stepStatus -isnot [string]) { $stepStatus = 'fail' $stepError = "Required Bicep $($def.Name) returned an invalid status; keep the registry static-validation jobs until this check runs." } elseif ($stepStatus -eq 'skipped') { $stepStatus = 'fail' $stepError = "Required Bicep $($def.Name) returned skipped; keep the registry static-validation jobs until this check runs." } elseif ($stepStatus -notin @('pass', 'fail', 'error')) { $stepError = "Required Bicep $($def.Name) returned an invalid status; keep the registry static-validation jobs until this check runs." $stepStatus = 'fail' } } if ($context.Ecosystem -eq 'bicep' -and $def.Name -eq 'docs' -and $stepStatus -eq 'pass') { $fields = @($stepResult.PSObject.Properties.Name) $validShape = 'FilesSelected' -in $fields -and 'FilesProcessed' -in $fields -and 'NotRendered' -in $fields -and 'Issues' -in $fields -and $stepResult.FilesSelected -is [int] -and $stepResult.FilesProcessed -is [int] -and $stepResult.FilesSelected -ge 0 -and $stepResult.FilesProcessed -ge 0 -and $stepResult.NotRendered -is [array] -and $stepResult.Issues -is [array] if (-not $validShape) { $stepStatus = 'fail' $stepError = 'avm.bicep.docs-result: Bicep docs reported pass without valid render counts, NotRendered, and Issues; keep the registry README check.' } elseif ($stepResult.FilesSelected -ne $stepResult.FilesProcessed) { $stepStatus = 'fail' $stepError = "avm.bicep.docs-result: Bicep docs rendered $($stepResult.FilesProcessed) of $($stepResult.FilesSelected) selected READMEs; keep the registry README check." } elseif (@($stepResult.Issues | Where-Object { $null -eq $_ -or $_.PSObject.Properties.Name -notcontains 'Severity' -or $_.Severity -isnot [string] -or $_.Severity -notin @('warning', 'notice', 'info') }).Count -gt 0) { $stepStatus = 'fail' $stepError = 'avm.bicep.docs-result: Bicep docs reported pass with an error or unclassified issue; keep the registry README check.' } } } catch [AvmNotSupportedException] { if ($requiredBicepStep) { $stepStatus = 'fail' $stepError = "Required Bicep $($def.Name) is not implemented: $($_.Exception.Message)" } else { $stepStatus = 'skipped' $stepError = $_.Exception.Message } } catch [AvmConfigurationException] { # The repo is misconfigured, not unsupported. This must fail rather # than skip: a skip renders as a benign gauntlet pass, which is how # a step that never actually ran gets to look green. $stepStatus = 'fail' $stepError = $_.Exception.Message } catch { $stepStatus = 'error' $stepError = if ($null -ne $_.ErrorDetails -and -not [string]::IsNullOrWhiteSpace($_.ErrorDetails.Message)) { $_.ErrorDetails.Message } else { $_.Exception.Message } } $stepSw.Stop() $stepEnd = $stepStart.AddMilliseconds($stepSw.Elapsed.TotalMilliseconds) $completionLevel = if ($stepStatus -eq 'pass') { 'Pass' } elseif ($stepStatus -in @('fail', 'error')) { 'Fail' } else { 'Info' } Write-AvmLog ('step {0}/{1}: {2} -> {3} ({4})' -f $stepIndex, $stepDefs.Count, $def.Name, $stepStatus, (Format-AvmDuration -Duration $stepSw.Elapsed)) -Level $completionLevel | Out-Null if ($stepStatus -in @('fail', 'error') -and -not [string]::IsNullOrWhiteSpace($stepError)) { # F41: narration only. Assert-AvmCommandSuccess promotes the same # text to the single GitHub Actions annotation for the run. Write-AvmLog (' {0}: {1}' -f $def.Name, $stepError) -Level Fail | Out-Null } $steps.Add([pscustomobject][ordered]@{ Step = $def.Name Status = $stepStatus Error = $stepError Result = $stepResult StartTime = $stepStart EndTime = $stepEnd DurationMs = [int]$stepSw.Elapsed.TotalMilliseconds }) if ($stepStatus -eq 'fail' -or $stepStatus -eq 'error') { $overall = $stepStatus } if ($stepStatus -eq 'error') { break } if ($def.Name -eq 'metadata' -and $stepStatus -ne 'pass') { break } if ($StopOnFail -and $stepStatus -eq 'fail') { break } } $sw.Stop() return [pscustomobject][ordered]@{ Path = $context.Root Ecosystem = $context.Ecosystem Status = $overall Steps = $steps.ToArray() StartTime = $startTime EndTime = $startTime.AddMilliseconds($sw.Elapsed.TotalMilliseconds) DurationMs = [int]$sw.Elapsed.TotalMilliseconds } } # SIG # Begin signature block # MIInQQYJKoZIhvcNAQcCoIInMjCCJy4CAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDqEW1qccqa3awV # FKTW8c+oHQ7pZh5wR44/z/ZgdnK/SKCCDLowggX1MIID3aADAgECAhMzAAACHU0Z # yE7XD1dIAAAAAAIdMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNVBAYTAlVTMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBD # b2RlIFNpZ25pbmcgUENBIDIwMjQwHhcNMjYwNDE2MTg1OTQzWhcNMjcwNDE1MTg1 # OTQzWjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYD # VQQDExVNaWNyb3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IB # DwAwggEKAoIBAQDQvewXxx9gZZFC6Ys1WBay8BJ8kGA4JQnH5CMafqOASlTpK9H8 # o5ZXTXt0caVQTNMUPt445wXYD+dFtaKWTwDn1I52oUSrC9vJin1Gsqt+zyKJL5Dg # 3eQXbQNR61DmMy20GLTIO3SFed9Rfi/ophgCLGFLDR3r0KvHjwMb/jYWS0celV/4 # Lz27LfAekm8v9E5IXaeiXbAUYZKK090n4CVl3JBtbN+9DtI9SNu/yjvozW52/u7R # X/Ttpa/KDlpuokZ+Zcbvmtd9ur9gFLvZzh41o9MsE/clQtdaFWGvuo6Jua/ntpgk # ey3E5/vBFe+MJPG6phdnuo6r57ZudCudiI1bAgMBAAGjggGbMIIBlzAOBgNVHQ8B # Af8EBAMCB4AwHwYDVR0lBBgwFgYKKwYBBAGCN0wIAQYIKwYBBQUHAwMwHQYDVR0O # BBYEFH6QuMwqcPG0hQlQ6c5jCtTTLrVeMEUGA1UdEQQ+MDykOjA4MR4wHAYDVQQL # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xFjAUBgNVBAUTDTIzMDAxMis1MDc1NTkw # HwYDVR0jBBgwFoAUf1k/VCHarU/vBeXmo9ctBpQSCDEwYAYDVR0fBFkwVzBVoFOg # UYZPaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0 # JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNybDBtBggrBgEFBQcBAQRh # MF8wXQYIKwYBBQUHMAKGUWh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMv # Y2VydHMvTWljcm9zb2Z0JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNy # dDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4ICAQBKTbYOjzwTG/DXGaz9 # s6+fQeaTtDcFmMY+5UyVFCyj7Pv+5i37qfX8lSL/tBIfYQfWsMuBQlfZurJD6r4H # VJ2CeH+1fgiq8dcHdVKoZ3Sa2qXoX3cq9iS8cVb06B7+5/XJ7I0OxHH9fDsvJ3T3 # w5V/ZtAIFmLrl+P0CtG+92uzRsn0nTbdFjOkLMLWPLAU3THohKRlSEMgFJpPkm5n # 5UAZ35xX6FWCrDLsSKb555bTifwa8mJBwdlof0bmfYidH+dxZ1FdDxvLnNl9zeKs # A4kejaaIqqIPguhwAti5Ql7BlTNoJNwxCvBmqW2MQLnCkYN/VVUsR3V2x/rcTNzo # Bf/Z/SpROvdaA2ZOOd1uioXJt3tdLQ7vHpqpib0KfWr/FWXW10q38VxfCnRQBqzb # SuztR7nEMuzX7Ck+B/XaPDXd1qh72+QYyB0Z2VzWmO9zsnb9Uq/dwu8LGeQqnyu6 # 7SDGACvnXii2fb9+US492VTnXSnFKyqwgzUyFMtZK1/sHYTv6bG4TtQUygQxTN+Z # V+aJIlKO2MqZ7bKrAnOzS9m6NgoTdWOq11bTOZwKlIEV/EhV9SWkDmdpR/hPPT2v # 6TEj4F8PT/zHjRezIU5c/DGlt/VhY/pK0XkJtEyMmmS1BMtjU/rqBZVMIm3dnxQs # /TBByr+Cf8Z1r7aifQVQ+WSqzjCCBr0wggSloAMCAQICEzMAAAA5O7Y3Gb8GHWcA # AAAAADkwDQYJKoZIhvcNAQEMBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX # YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg # Q29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRl # IEF1dGhvcml0eSAyMDExMB4XDTI0MDgwODIwNTQxOFoXDTM2MDMyMjIyMTMwNFow # VzELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEo # MCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAyNDCCAiIwDQYJ # KoZIhvcNAQEBBQADggIPADCCAgoCggIBANgBnB7jOMeqlRYHNa265v4IY9fH8TKh # emHfPINe1gpLaV3dhg324WwH06LcHbpnsBukCDNitryo0dtS/EW6I/yEL/bLSY8h # KpbfQuWusBPr9qazYcDxCW/qnjb5JsI1s8bNOg3bVATvQVL4tcf03aTycsz8QeCd # M0l/yHRObJ9QqazM1r6VPEOJ7LL+uEEb73w6QCuhs89a1uv1zerOYMnsneRRwCbp # yW11IcggU0cRKDDq1pjVJzIbIF6+oiXXbReOsgeI8zu1FyQfK0fVkaya8SmVHQ/t # Of23mZ4W9k0Ri22QW9p3UgSC5OUDktKxxcCmGL6tXLfOGSWHIIV4YrTJTT6PNty5 # REojHJuZHArkF9VnHTERWoTjAzfI3kP+5b4alUdhgAZ7ttOu1bVnXfHaqPYl2rPs # 20ji03LOVWsh/radgE17es5hL+t6lV0eVHrVhsssROWJuz2MXMCt7iw7lFPG9LXK # Gjsmonn2gotGdHIuEg5JnJMJVmixd5LRlkmgYRZKzhxSCwyoGIq0PhaA7Y+VPct5 # pCHkijcIIDm0nlkK+0KyepolcqGm0T/GYQRMhHJlGOOmVQop36wUVUYklUy++vDW # eEgEo4s7hxN6mIbf2MSIQ/iIfMZgJxC69oukMUXCrOC3SkE/xIkgpfl22MM1itkZ # 35nNXkMolU1lAgMBAAGjggFOMIIBSjAOBgNVHQ8BAf8EBAMCAYYwEAYJKwYBBAGC # NxUBBAMCAQAwHQYDVR0OBBYEFH9ZP1Qh2q1P7wXl5qPXLQaUEggxMBkGCSsGAQQB # gjcUAgQMHgoAUwB1AGIAQwBBMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU # ci06AjGQQ7kUBU7h6qfHMdEjiTQwWgYDVR0fBFMwUTBPoE2gS4ZJaHR0cDovL2Ny # bC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0MjAx # MV8yMDExXzAzXzIyLmNybDBeBggrBgEFBQcBAQRSMFAwTgYIKwYBBQUHMAKGQmh0 # dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0MjAx # MV8yMDExXzAzXzIyLmNydDANBgkqhkiG9w0BAQwFAAOCAgEAFJQfOChP7onn6fLI # MKrSlN1WYKwDFgAddymOUO3FrM8d7B/W/iQ6DxXsDn7D5W4wMwYeLystcEqfkjz4 # NURRgazyMu5yRzQh4LqjA4tStTcJh1opExo7nn5PuPBYnbu0+THSuVHTe0VTTPVh # ily/piFrDo3axQ9P4C+Ol5yet+2gTfekICS5xS+cYfSIvgn0JksVBVMYVI5QFu/q # hnLhsEFEUzG8fvv0hjgkO+lkpV9ty6GkN4vdnd7ya6Q6aR9y34aiM1qmxaxBi6OU # nyNl6fkuun/diTFnYDLTppOkr/mg5WSfCiDVMNCxtj4wPKC5OmHm1DQIt/MNokbb # H3UGsFP1QbzsLocuSqLCvH09Io3fDPTmscR9Y75G4qX7RTX8AdBPo0I6OEojf39z # uFZt0qOHm65YWQE69cZM2ueE1MB05dNNgHK9gTE7zKvK/fg8B2qjW88MT/WF5V5u # vZGtqa9FSL2RazArA+rDPuf6JGYz4HpgMZHB4S6szWSKYBv0VisCzfxgeU+dquXW # 9bd0auYlOB58DPcOYKdc3Se94g+xL4pcEhbB54JOgAkwYTu/9dLeH2pDqeJZAABV # DWRQCaXfO5LgyKwKCLYXpigrZYCjUSBcr+Ve8PFWMhVTQl0v4q8J/AUmQN5W4n10 # 1cY2L4A7GTQG1h32HHAvfQESWP0xghndMIIZ2QIBATBuMFcxCzAJBgNVBAYTAlVT # MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jv # c29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMjQCEzMAAAIdTRnITtcPV0gAAAAAAh0w # DQYJYIZIAWUDBAIBBQCggZAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwLwYJ # KoZIhvcNAQkEMSIEIEpgqZVr378V/o7b+VJ8CaHX3EgrhOJ6PNg2jH1tyt2tMEIG # CisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8v # d3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEBBQAEggEAakWO8lyfC9nm3vpG # YyMLyYdEVMqmYoSpN6sVt5ZWDkd8Z6cqFnJdH0OZItdz1DGabWYQAAkTGbj2pIRV # YiXuMl0Okf7Hbx6jQmLH5HOkIzuv6WHndsEQQUePM+7YcpyZRQtPYgezUkhOFbQ5 # 6YRat4btlurZef08OyekTeoOl00zJ8e14UFDBz3ZwNrhBzEr2+ngTKDrVYOcB+VJ # eG7kuXoLelG9qeKoPfqDiIlDwEqtPxKuEY4NVp+o1JAym8lf+gjJmh9C2Aj/F0hL # 1/ySQCITP1Q51+gDFu5sy99JLl26axoSVs6LOrq0ATADD/N18vD5XF6FqU9dK7NF # ke4C6KGCF60wghepBgorBgEEAYI3AwMBMYIXmTCCF5UGCSqGSIb3DQEHAqCCF4Yw # gheCAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFaBgsqhkiG9w0BCRABBKCCAUkEggFF # MIIBQQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFlAwQCAQUABCCJiOzxLh1b2Bgb # bkrlMlGztL2AseHAOmVGjh32VXMtGQIGaq9WATcrGBMyMDI2MTAxNTE5MDMwNi41 # MzhaMASAAgH0oIHZpIHWMIHTMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu # Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv # cmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExp # bWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjo2NTFBLTA1RTAtRDk0NzEl # MCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaCCEfswggcoMIIF # EKADAgECAhMzAAACFRgD04EHJnxTAAEAAAIVMA0GCSqGSIb3DQEBCwUAMHwxCzAJ # BgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25k # MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jv # c29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTI1MDgxNDE4NDgyMFoXDTI2MTEx # MzE4NDgyMFowgdMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw # DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x # LTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEn # MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjY1MUEtMDVFMC1EOTQ3MSUwIwYDVQQD # ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIICIjANBgkqhkiG9w0BAQEF # AAOCAg8AMIICCgKCAgEAw3HV3hVxL0lEYPV03XeNKZ517VIbgexhlDPdpXwDS0BY # txPwi4XYpZR1ld0u6cr2Xjuugdg50DUx5WHL0QhY2d9vkJSk02rE/75hcKt91m2I # h287QRxRMmFu3BF6466k8qp5uXtfe6uciq49YaS8p+dzv3uTarD4hQ8UT7La95pO # JiRqxxd0qOGLECvHLEXPXioNSx9pyhzhm6lt7ezLxJeFVYtxShkavPoZN0dOCiYe # h4KgoKoyagzMuSiLCiMUW4Ue4Qsm658FJNGTNh7V5qXYVA6k5xjw5WeWdKOz0i9A # 5jBcbY9fVOo/cA8i1bytzcDTxb3nctcly8/OYeNstkab/Isq3Cxe1vq96fIHE1+Z # GmJjka1sodwqPycVp/2tb+BjulPL5D6rgUXTPF84U82RLKHV57bB8fHRpgnjcWBQ # uXPgVeSXpERWimt0NF2lCOLzqgrvS/vYqde5Ln9YlKKhAZ/xDE0TLIIr6+I/2JTt # XP34nfjTENVqMBISWcakIxAwGb3RB5yHCxynIFNVLcfKAsEdC5U2em0fAvmVv0so # nqnv17cuaYi2eCLWhoK1Ic85Dw7s/lhcXrBpY4n/Rl5l3wHzs4vOIhu87DIy5QUa # EupEsyY0NWqgI4BWl6v1wgse+l8DWFeUXofhUuCgVTuTHN3K8idoMbn8Q3edUIEC # AwEAAaOCAUkwggFFMB0GA1UdDgQWBBSJIXfxcqAwFqGj9jdwQtdSqadj1zAfBgNV # HSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBfBgNVHR8EWDBWMFSgUqBQhk5o # dHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBU # aW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmwwbAYIKwYBBQUHAQEEYDBeMFwG # CCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRz # L01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNydDAMBgNV # HRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMA4GA1UdDwEB/wQEAwIH # gDANBgkqhkiG9w0BAQsFAAOCAgEAd42HtV+kGbvxzLBTC5O7vkCIBPy/BwpjCzeL # 53hAiEOebp+VdNnwm9GVCfYq3KMfrj4UvKQTUAaS5Zkwe1gvZ3ljSSnCOyS5OwNu # 9dpg3ww+QW2eOcSLkyVAWFrLn6Iig3TC/zWMvVhqXtdFhG2KJ1lSbN222csY3E3/ # BrGluAlvET9gmxVyyxNy59/7JF5zIGcJibydxs94JL1BtPgXJOfZzQ+/3iTc6eDt # maWT6DKdnJocp8wkXKWPIsBEfkD6k1Qitwvt0mHrORah75SjecOKt4oWayVLkPTh # o12e0ongEg1cje5fxSZGthrMrWKvI4R7HEC7k8maH9ePA3ViH0CVSSOefaPTGMzI # hHCo5p3jG5SMcyO3eA9uEaYQJITJlLG3BwwGmypY7C/8/nj1SOhgx1HgJ0ywOJL9 # xfP4AOcWmCfbsqgGbCaC7WH5sINdzfMar8V7YNFqkbCGUKhc8GpIyE+MKnyVn33j # suaGAlNRg7dVRUSoYLJxvUsw9GOwyBpBwbE9sqOLm+HsO00oF23PMio7WFXcFTZA # jp3ujihBAfLrXICgGOHPdkZ042u1LZqOcnlr3XzvgMe+mPPyasW8f0rtzJj3V5E/ # EKiyQlPxj9Mfq2x9himnlXWGZCVPeEBROrNbDYBfazTyLNCOTsRtksOSV3FBtPnp # QtLN754wggdxMIIFWaADAgECAhMzAAAAFcXna54Cm0mZAAAAAAAVMA0GCSqGSIb3 # DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G # A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIw # MAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAx # MDAeFw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMyMjVaMHwxCzAJBgNVBAYTAlVT # MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1l # LVN0YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA # 5OGmTOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51yMo1V/YBf2xK4OK9uT4XYDP/ # XE/HZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY6GB9alKDRLemjkZrBxTzxXb1 # hlDcwUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9cmmvHaus9ja+NSZk2pg7uhp7 # M62AW36MEBydUv626GIl3GoPz130/o5Tz9bshVZN7928jaTjkY+yOSxRnOlwaQ3K # Ni1wjjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDuaRr3tpK56KTesy+uDRedGbsoy # 1cCGMFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74kpEeHT39IM9zfUGaRnXNxF80 # 3RKJ1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2K26oElHovwUDo9Fzpk03dJQc # NIIP8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5TI4CvEJoLhDqhFFG4tG9ahha # YQFzymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZki1ugpoMhXV8wdJGUlNi5UPkL # iWHzNgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9QBXpsxREdcu+N+VLEhReTwDwV # 2xo3xwgVGD94q0W29R6HXtqPnhZyacaue7e3PmriLq0CAwEAAaOCAd0wggHZMBIG # CSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUCBBYEFCqnUv5kxJq+gpE8RjUp # zxD/LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJlpxtTNRnpcjBcBgNVHSAEVTBT # MFEGDCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jv # c29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9yeS5odG0wEwYDVR0lBAwwCgYI # KwYBBQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGG # MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186a # GMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3Br # aS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsG # AQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29t # L3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwDQYJKoZIhvcN # AQELBQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/ypb+pcFLY+TkdkeLEGk5c9MTO1 # OdfCcTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulmZzpTTd2YurYeeNg2LpypglYA # A7AFvonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM9W0jVOR4U3UkV7ndn/OOPcbz # aN9l9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECWOKz3+SmJw7wXsFSFQrP8DJ6L # GYnn8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4FOmRsqlb30mjdAy87JGA0j3m # Sj5mO0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3UwxTSwethQ/gpY3UA8x1RtnWN0 # SCyxTkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPXfx5bRAGOWhmRaw2fpCjcZxko # JLo4S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVXVAmxaQFEfnyhYWxz/gq77EFm # PWn9y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGConsXHRWJjXD+57XQKBqJC482 # 2rpM+Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU5nR0W2rRnj7tfqAxM328y+l7 # vzhwRNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEGahC0HVUzWLOhcGbyoYIDVjCC # Aj4CAQEwggEBoYHZpIHWMIHTMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu # Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv # cmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExp # bWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjo2NTFBLTA1RTAtRDk0NzEl # MCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsO # AwIaAxUAj6eTejbuYE1Ifjbfrt6tXevCUSCggYMwgYCkfjB8MQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGlt # ZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQsFAAIFAO57b8UwIhgPMjAyNjEw # MTUxNTI5MDlaGA8yMDI2MTAxNjE1MjkwOVowdDA6BgorBgEEAYRZCgQBMSwwKjAK # AgUA7ntvxQIBADAHAgEAAgIiaDAHAgEAAgISgzAKAgUA7nzBRQIBADA2BgorBgEE # AYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYag # MA0GCSqGSIb3DQEBCwUAA4IBAQBs5hpVOhELr8yYeyCVPPgVlnGyz72WEGIcKg7P # Lvm9InrOTHKxGfsKJlQ/8HPFH44WRAYevjv5Kfhj0+YSEV+kieLuwKiLMuU1Eca7 # jwZtyAbE4isrHTHRsBn3ItUR843J8oCmFOxFGaRYS+kCSpJrV0OGGhUgAE9MPtu3 # /HXLA3UsEd+Tk3aaxpzOXuuEwGklC/SROw4ea9rr4eyD2hVWN1WOkwISvXJtLphV # Ro0LD8en0VKvbPPLZ+7GXwTntwaAcwe9pcsbxgN1kx/PKPtyt2nZI8rgZw7An2y1 # QXtqMoyLWxnePKJOqXREvYc6DCM2y0LyJmKt90M8pA1Xm0UpMYIEDTCCBAkCAQEw # gZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT # B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UE # AxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAIVGAPTgQcmfFMA # AQAAAhUwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0B # CRABBDAvBgkqhkiG9w0BCQQxIgQg7JAqSzIaWFRr5D9ljDKNZmtGGTKVXYjphCtc # /1+A6/IwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCBwEPR2PDrTFLcrtQsK # rUi7oz5JNRCF/KRHMihSNe7sijCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w # IFBDQSAyMDEwAhMzAAACFRgD04EHJnxTAAEAAAIVMCIEIFG1Sp9AEtgb1Yog2jO7 # Y6aZb6Hk6hcFheq0H3mMHmO8MA0GCSqGSIb3DQEBCwUABIICACa9VDhdW0Gp5GGM # agevWPGALH3TLSMLh6Y5By2UhGy6LHYevFf/0YnrncZTeBXe2dRwZ9TTxF0RHilS # HOl7KgaOjSffqMJClAHcZBoTp5yrXlWgNq/vHeU68vnzpjpRS6tqepge0Ad6U+LF # HCrtanJoZPCkeRUHfraIFSVKsDy0m55W3NW0zzm45FuSmPfJHmdoquizoMgkymG2 # Z07e+RwT6R0HgvwDEPvMT2B/QvHI5ELH9iEToqqpMB6wDIsUut3MWbiWFrvVMVMM # 2JYwlIMi9JQ1bUYCsHB7d2lMnhw0r99Fc/IS6OJBJRsmFYSdTu/qzgeCV42sAgQj # Hr12XCSGKgpQWWMm5qjhkI4l5hWaXnQ3ae/mrUKrIGcr33T5V1zTmopmDhV4j/OS # USie9kLbjD/030G1C2l/z0QgFImX/gdORGRtVre5yZ0WYsHc3ccg21ZcSIIwXAvj # rQmHy03gJeesmb4YGZnZy66aFFVyUQknu8s5kzdfq+Pc/hkMtGFTCeqPf6Ln2eJo # hX3jPsWUCvRbnnIarzNB/MR0ggirk4j/1bQHKUYsZkV39+SlVdidLI30kd1sh4KD # lStvIM001XQiXPQ6/3MVRFL/ywT1P8geQOllzaC+WiG9Wmvxeoreg6acA47XNTGE # OqL+yqyUMn/qZaWIGLE48r8hRAdV # SIG # End signature block |