Engines/ManagedFiles/Sync-AvmManagedFile.ps1

function Sync-AvmManagedFile {
    <#
    .SYNOPSIS
        Synchronise a Terraform module repository's managed files against the
        AVM governance source-of-truth, applying additions, updates, and
        deprecated-file removals directly to the working tree.

    .DESCRIPTION
        Engine implementation behind Invoke-AvmSync. Ported from the
        Azure/avm-terraform-governance repo-sync tooling, reduced to the
        *file-sync* concern only: it never opens or merges a pull request, it
        mutates the local working tree ($Context.Root) in place.

        Source of the managed files (highest precedence first):

          1. Explicit cmdlet parameters.
          2. Environment variables (AVM_MANAGED_FILES_*).
          3. A repo-committed '.avm/managed-files.json' under $Context.Root.
          4. Defaults: the public Azure/avm-terraform-governance repo, 'main'
             ref, 'managed-files' base folder, and 'tf-repo-mgmt/repository-config'
             config folder.

        A direct local path (-ManagedFilesLocalPath or
        AVM_MANAGED_FILES_LOCAL_PATH) short-circuits the git fetch entirely and
        is what the offline tests use. Otherwise the source repo is shallow
        cloned (or fetched, if already cached) into $env:AVM_HOME/cache.

        The managed-file map is built from '<base>/root' plus zero or more
        overlays ('<base>/<overlay>') stacked in declaration order, where later
        sources win, minus any excluded paths. A source subtree at
        '<parent>/_all/' is broadcast into every existing immediate child of
        the matching target parent; reserved '_all' segments are never copied
        literally. A root-level '_all/' remains a literal path. Overlays and
        exclusions are resolved from the config folder's 'config.json' by
        matching the repository id against 'repositoryGroups'.
        'deprecated-files.json' lists paths that must be removed from every
        target repo; deprecated removals win over managed adds when both name
        the same path.

        For each desired managed file the engine computes git's blob SHA-1 over
        the source bytes and compares it (plus the git index mode) with the
        on-disk state under $Context.Root:

          - Add = desired path absent on disk.
          - Update = desired path present but blob SHA or mode differs.
          - Remove = deprecated path present on disk (file or directory).

        With -CheckDrift the engine writes nothing: any needed change makes the
        aggregate Status 'fail' and is recorded as an Issue (the pr-check hard
        gate). Otherwise it applies the changes (honouring -WhatIf /
        SupportsShouldProcess) and returns Status 'pass'.

    .PARAMETER Context
        Module context produced by Get-AvmModuleContext. Must have
        Ecosystem='terraform'. Its Root is the working tree that is synced.

    .PARAMETER AllowPathFallback
        Accepted for signature parity with the other engines. The managed-files
        engine shells out to plain 'git' (not a pinned AVM tool), so this switch
        is currently a no-op.

    .PARAMETER CheckDrift
        Report-only mode. No files are written; any required change flips the
        Status to 'fail' and is emitted as an Issue.

    .PARAMETER ManagedFilesRepo
        owner/name of the git repo that holds the managed files. Defaults to
        'Azure/avm-terraform-governance'.

    .PARAMETER ManagedFilesRef
        Git ref (branch/tag/sha) to fetch. Defaults to 'main'.

    .PARAMETER ManagedFilesPath
        Path within the source repo to the managed-files base folder (the one
        that contains 'root/' and the overlays). Defaults to 'managed-files'.

    .PARAMETER ManagedFilesLocalPath
        Direct local path to the managed-files base folder. When supplied the
        git fetch is skipped entirely.

    .PARAMETER ConfigRepo
        owner/name of the git repo that holds the config folder. Defaults to
        the same repo as ManagedFilesRepo.

    .PARAMETER ConfigRef
        Git ref for the config repo. Defaults to the same ref as
        ManagedFilesRef.

    .PARAMETER ConfigPath
        Path within the config repo to the folder holding 'config.json' and
        'deprecated-files.json'. Defaults to 'tf-repo-mgmt/repository-config'.

    .PARAMETER ConfigLocalPath
        Direct local path to the config folder. When supplied no config repo is
        fetched.

    .PARAMETER RepoId
        The repository id used to look up overlays/exclusions in config.json.
        When omitted it is resolved by Resolve-AvmManagedFilesRepoId: an explicit
        AVM_MANAGED_FILES_REPO_ID environment value or '.avm/managed-files.json'
        repoId override is authoritative; otherwise a candidate is derived from
        the git origin remote, then the working-tree folder name, with a leading
        'terraform-azurerm-' / 'terraform-azapi-' prefix stripped. Matching a
        config.json repositoryGroups entry selects overlays and exclusions; an
        unmatched id still receives the shared root files. Resolution fails only
        when no repository id can be determined.

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

    [CmdletBinding(SupportsShouldProcess)]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
        'PSReviewUnusedParameter', 'AllowPathFallback',
        Justification = 'Accepted for cross-verb parity and forwarded by Invoke-AvmSync; this engine shells out to plain git (Get-Command) rather than an AVM-pinned tool, so there is no resolved tool path to fall back on.')]
    [OutputType([pscustomobject])]
    param(
        [Parameter(Mandatory)]
        $Context,

        [switch] $AllowPathFallback,

        [switch] $CheckDrift,

        [string] $ManagedFilesRepo,
        [string] $ManagedFilesRef,
        [string] $ManagedFilesPath,
        [string] $ManagedFilesLocalPath,

        [string] $ConfigRepo,
        [string] $ConfigRef,
        [string] $ConfigPath,
        [string] $ConfigLocalPath,

        [string] $RepoId
    )

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

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

    $root = $Context.Root

    $settings = Resolve-AvmManagedFilesSetting `
        -Root $root `
        -ManagedFilesRepo $ManagedFilesRepo `
        -ManagedFilesRef $ManagedFilesRef `
        -ManagedFilesPath $ManagedFilesPath `
        -ManagedFilesLocalPath $ManagedFilesLocalPath `
        -ConfigRepo $ConfigRepo `
        -ConfigRef $ConfigRef `
        -ConfigPath $ConfigPath `
        -ConfigLocalPath $ConfigLocalPath `
        -RepoId $RepoId

    $gitPath = (Get-Command -Name 'git' -CommandType Application -ErrorAction SilentlyContinue |
            Select-Object -First 1).Source

    $source = Resolve-AvmManagedFilesSource -Settings $settings -GitPath $gitPath
    Write-AvmLog ("sync: source kind={0}; managed-files={1}; config={2}" -f $source.SourceKind, $source.ManagedBaseDir, $source.ConfigDir) -Level Verbose | Out-Null

    $overlays = @()
    $excluded = @()
    $deprecated = @()
    $repositoryConfig = $null
    if ($source.ConfigDir -and (Test-Path -LiteralPath $source.ConfigDir -PathType Container)) {
        $configFile = Join-Path $source.ConfigDir 'config.json'
        if (Test-Path -LiteralPath $configFile -PathType Leaf) {
            $repositoryConfig = Get-Content -LiteralPath $configFile -Raw | ConvertFrom-Json
        }
        $deprecatedFile = Join-Path $source.ConfigDir 'deprecated-files.json'
        if (Test-Path -LiteralPath $deprecatedFile -PathType Leaf) {
            $deprecated = @(Get-Content -LiteralPath $deprecatedFile -Raw | ConvertFrom-Json)
        }
    }

    $repoId = Resolve-AvmManagedFilesRepoId `
        -Root $root `
        -ExplicitRepoId $settings.RepoId `
        -KnownRepoIds (Get-AvmManagedFilesKnownRepoId -RepositoryConfig $repositoryConfig) `
        -GitPath $gitPath `
        -Interactive (Test-AvmManagedFilesInteractive)

    if ($repositoryConfig) {
        $resolved = Resolve-AvmManagedFilesRepositorySetting -RepositoryConfig $repositoryConfig -RepoId $repoId
        $overlays = $resolved.ManagedFilesAdditional
        $excluded = $resolved.ExcludedManagedFiles
    }
    Write-AvmLog ("sync: repo-id={0}; overlays={1}; exclusions={2}; deprecated-candidates={3}" -f $repoId, $overlays.Count, $excluded.Count, $deprecated.Count) -Level Verbose | Out-Null

    $map = Build-AvmManagedFilesMap `
        -BaseDir $source.ManagedBaseDir `
        -TargetRoot $root `
        -Overlays $overlays `
        -Excluded $excluded `
        -RepoId $repoId `
        -GitPath $gitPath

    $desired = Get-AvmDesiredManagedFile -ManagedFiles $map

    # Deprecated removals win over managed adds if both name the same path.
    $matchedDeprecated = @(Get-AvmMatchingDeprecatedPath -CandidatePaths $deprecated -Root $root)
    $deprecatedLookup = @{}
    foreach ($p in $matchedDeprecated) { $deprecatedLookup[$p] = $true }
    foreach ($p in @($desired.Keys)) {
        if ($deprecatedLookup.ContainsKey($p)) { $desired.Remove($p) | Out-Null }
    }
    Write-AvmLog ("sync: desired files={0}; matched deprecated files={1}" -f $desired.Count, $matchedDeprecated.Count) -Level Verbose | Out-Null

    # Line-managed files (e.g. .gitignore) are merged line-by-line rather than
    # overwritten wholesale, so the consumer keeps its own additions. The spec
    # stacks across root + overlays like the files themselves. A path owned by
    # the line spec must not also be whole-file managed (line-merge wins), and a
    # deprecated removal still trumps a line merge.
    $lineSpec = Get-AvmManagedLineSpec -BaseDir $source.ManagedBaseDir -Overlays $overlays
    foreach ($p in @($lineSpec.Keys)) {
        if ($deprecatedLookup.ContainsKey($p)) { $lineSpec.Remove($p) | Out-Null }
    }
    foreach ($p in @($lineSpec.Keys)) {
        if ($desired.ContainsKey($p)) { $desired.Remove($p) | Out-Null }
    }
    $linePlans = @(Get-AvmManagedLinePlan -Root $root -Spec $lineSpec)
    $changedLinePlans = @($linePlans | Where-Object { $_.Changed })

    $targetModes = Get-AvmGitIndexMode -Dir $root -GitPath $gitPath
    $existingBlobs = @{}
    $existingModes = @{}
    foreach ($targetPath in $desired.Keys) {
        $full = Join-Path $root ($targetPath.Replace('/', [System.IO.Path]::DirectorySeparatorChar))
        if (Test-Path -LiteralPath $full -PathType Leaf) {
            $bytes = [System.IO.File]::ReadAllBytes($full)
            $existingBlobs[$targetPath] = Get-AvmGitBlobSha -Bytes $bytes
            $mode = $targetModes[$targetPath]
            if (-not $mode) { $mode = '100644' }
            $existingModes[$targetPath] = $mode
        }
    }

    $toAdd = @()
    $toUpdate = @()
    foreach ($targetPath in ($desired.Keys | Sort-Object)) {
        $desiredSha = $desired[$targetPath].Sha
        $desiredMode = $desired[$targetPath].Mode
        if (-not $existingBlobs.ContainsKey($targetPath)) {
            $toAdd += $targetPath
        }
        else {
            $existingSha = $existingBlobs[$targetPath]
            $existingMode = $existingModes[$targetPath]
            if (-not $existingMode) { $existingMode = '100644' }
            if ($existingSha -ne $desiredSha -or $existingMode -ne $desiredMode) {
                $toUpdate += $targetPath
            }
        }
    }
    $toRemove = @($matchedDeprecated | Sort-Object)

    $lineAdded = @($changedLinePlans | Where-Object { -not $_.Existed } | ForEach-Object { $_.Path } | Sort-Object)
    $lineUpdated = @($changedLinePlans | Where-Object { $_.Existed } | ForEach-Object { $_.Path } | Sort-Object)
    Write-AvmLog ("sync: plan add={0}; update={1}; remove={2}; line-merge={3}" -f $toAdd.Count, $toUpdate.Count, $toRemove.Count, $changedLinePlans.Count) -Level Verbose | Out-Null

    $issues = @()
    if ($CheckDrift) {
        Write-AvmLog 'sync: check-drift mode; reporting planned changes without writing' -Level Verbose | Out-Null
        $issueList = New-Object System.Collections.Generic.List[object]
        foreach ($p in $toRemove) {
            $issueList.Add((New-AvmSyncIssue -File $p -Message 'deprecated file present in the repository; it should be removed.'))
        }
        foreach ($p in $toAdd) {
            $issueList.Add((New-AvmSyncIssue -File $p -Message 'managed file missing from the repository; it should be added.'))
        }
        foreach ($p in $toUpdate) {
            $issueList.Add((New-AvmSyncIssue -File $p -Message 'managed file is out of date; it should be updated.'))
        }
        foreach ($plan in $changedLinePlans) {
            $detail = ('managed lines out of date; {0} to add, {1} to remove.' -f $plan.AddedLines.Count, $plan.RemovedLines.Count)
            $issueList.Add((New-AvmSyncIssue -File $plan.Path -Message $detail))
        }
        $issues = $issueList.ToArray()
        $status = if ($issueList.Count -gt 0) { 'fail' } else { 'pass' }
    }
    else {
        $hasChanges = ($toAdd.Count + $toUpdate.Count + $toRemove.Count + $changedLinePlans.Count) -gt 0
        if ($hasChanges) {
            $applyDesc = ('sync managed files (add {0}, update {1}, remove {2}, merge-lines {3})' -f $toAdd.Count, $toUpdate.Count, $toRemove.Count, $changedLinePlans.Count)
            if ($PSCmdlet.ShouldProcess($root, $applyDesc)) {
                Write-AvmLog ("sync: applying managed-file plan to {0}" -f $root) -Level Verbose | Out-Null
                foreach ($p in $toRemove) {
                    $full = Join-Path $root ($p.Replace('/', [System.IO.Path]::DirectorySeparatorChar))
                    if (Test-Path -LiteralPath $full) {
                        Remove-Item -LiteralPath $full -Recurse -Force
                    }
                    Write-AvmLog 'sync: managed-file plan applied' -Level Verbose | Out-Null
                }
                foreach ($p in @($toAdd + $toUpdate)) {
                    $full = Join-Path $root ($p.Replace('/', [System.IO.Path]::DirectorySeparatorChar))
                    $parent = Split-Path -Parent $full
                    if ($parent -and -not (Test-Path -LiteralPath $parent)) {
                        New-Item -ItemType Directory -Path $parent -Force | Out-Null
                    }
                    [System.IO.File]::WriteAllBytes($full, $desired[$p].Bytes)

                    if ($desired[$p].Mode -eq '100755') {
                        Set-AvmManagedFileExecutableBit -Path $full
                    }
                }
                $lineEncoding = [System.Text.UTF8Encoding]::new($false)
                foreach ($plan in $changedLinePlans) {
                    $parent = Split-Path -Parent $plan.Full
                    if ($parent -and -not (Test-Path -LiteralPath $parent)) {
                        New-Item -ItemType Directory -Path $parent -Force | Out-Null
                    }
                    [System.IO.File]::WriteAllText($plan.Full, $plan.NewText, $lineEncoding)
                }
            }
        }
        $status = 'pass'
    }

    return [pscustomobject][ordered]@{
        Engine         = 'terraform'
        Tool           = 'managed-files'
        ToolPath       = $source.ToolPath
        ToolSource     = $source.SourceKind
        Status         = $status
        FilesProcessed = $desired.Count + $linePlans.Count
        Issues         = $issues
        Added          = @($toAdd + $lineAdded)
        Updated        = @($toUpdate + $lineUpdated)
        Removed        = $toRemove
    }
}

function Resolve-AvmManagedFilesSetting {
    <#
    .SYNOPSIS
        Resolve the effective managed-files settings by layering explicit
        parameters over environment variables, a repo-committed config file,
        and built-in defaults.
    #>

    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
        'PSUseShouldProcessForStateChangingFunctions', '',
        Justification = 'Factory function; returns a settings hashtable and mutates no external state.')]
    [OutputType([hashtable])]
    param(
        [Parameter(Mandatory)]
        [string] $Root,

        [string] $ManagedFilesRepo,
        [string] $ManagedFilesRef,
        [string] $ManagedFilesPath,
        [string] $ManagedFilesLocalPath,
        [string] $ConfigRepo,
        [string] $ConfigRef,
        [string] $ConfigPath,
        [string] $ConfigLocalPath,
        [string] $RepoId
    )

    $fileConfig = Get-AvmManagedFilesFileConfig -Root $Root

    $pick = {
        param([string]$Explicit, [string]$EnvName, [string]$FileKey, [string]$Default)
        if ($Explicit) { return $Explicit }
        $envValue = [System.Environment]::GetEnvironmentVariable($EnvName)
        if ($envValue) { return $envValue }
        if ($FileKey -and $fileConfig.ContainsKey($FileKey) -and $fileConfig[$FileKey]) { return [string]$fileConfig[$FileKey] }
        return $Default
    }

    $repo = & $pick $ManagedFilesRepo 'AVM_MANAGED_FILES_REPO' 'repo' 'Azure/avm-terraform-governance'
    $ref = & $pick $ManagedFilesRef 'AVM_MANAGED_FILES_REF' 'ref' 'main'
    $path = & $pick $ManagedFilesPath 'AVM_MANAGED_FILES_PATH' 'path' 'managed-files'
    $localPath = & $pick $ManagedFilesLocalPath 'AVM_MANAGED_FILES_LOCAL_PATH' 'localPath' ''

    $configRepoValue = & $pick $ConfigRepo 'AVM_MANAGED_FILES_CONFIG_REPO' 'configRepo' $repo
    $configRefValue = & $pick $ConfigRef 'AVM_MANAGED_FILES_CONFIG_REF' 'configRef' $ref
    $configPathValue = & $pick $ConfigPath 'AVM_MANAGED_FILES_CONFIG_PATH' 'configPath' 'tf-repo-mgmt/repository-config'
    $configLocalPath = & $pick $ConfigLocalPath 'AVM_MANAGED_FILES_CONFIG_LOCAL_PATH' 'configLocalPath' ''

    # RepoId is captured here only as its authoritative short-circuit value: an
    # explicit -RepoId parameter, the AVM_MANAGED_FILES_REPO_ID environment
    # variable, or a '.avm/managed-files.json' repoId override. Inference from the
    # git origin or the folder leaf happens later in
    # Resolve-AvmManagedFilesRepoId. Governance membership prioritises a matching
    # candidate but is optional because ungrouped repositories use root files.
    $repoIdValue = & $pick $RepoId 'AVM_MANAGED_FILES_REPO_ID' 'repoId' ''

    return @{
        ManagedFilesRepo      = $repo
        ManagedFilesRef       = $ref
        ManagedFilesPath      = $path
        ManagedFilesLocalPath = $localPath
        ConfigRepo            = $configRepoValue
        ConfigRef             = $configRefValue
        ConfigPath            = $configPathValue
        ConfigLocalPath       = $configLocalPath
        RepoId                = $repoIdValue
    }
}

function ConvertTo-AvmManagedFilesRepoId {
    <#
    .SYNOPSIS
        Normalise a repository name into a managed-files repository id by
        stripping a leading 'terraform-azurerm-' / 'terraform-azapi-' prefix.
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param(
        [AllowEmptyString()]
        [AllowNull()]
        [string] $Name
    )

    if ([string]::IsNullOrWhiteSpace($Name)) { return '' }

    $value = $Name.Trim()
    foreach ($prefix in @('terraform-azurerm-', 'terraform-azapi-')) {
        if ($value.StartsWith($prefix)) {
            $value = $value.Substring($prefix.Length)
            break
        }
    }

    return $value
}

function Get-AvmRepoLeafFromUrl {
    <#
    .SYNOPSIS
        Extract the repository leaf name from a git remote URL, handling HTTPS
        (with or without a trailing '.git'), SCP-style SSH
        (git@host:owner/repo.git) and ssh:// URLs.
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param(
        [AllowEmptyString()]
        [AllowNull()]
        [string] $Url
    )

    if ([string]::IsNullOrWhiteSpace($Url)) { return '' }

    $value = $Url.Trim().TrimEnd('/')
    $leaf = @($value -split '[:/]' | Where-Object { $_ }) | Select-Object -Last 1
    if ([string]::IsNullOrWhiteSpace($leaf)) { return '' }

    if ($leaf.EndsWith('.git')) {
        $leaf = $leaf.Substring(0, $leaf.Length - 4)
    }

    return $leaf
}

function Get-AvmManagedFilesOriginRepoId {
    <#
    .SYNOPSIS
        Resolve a normalised repository id from the 'origin' git remote of a
        working tree, or an empty string when there is no origin/git available.
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory)]
        [string] $Root,

        [string] $GitPath
    )

    if ([string]::IsNullOrWhiteSpace($GitPath) -or [string]::IsNullOrWhiteSpace($Root)) {
        return ''
    }

    try {
        $result = Invoke-AvmProcess -FilePath $GitPath -ArgumentList @('-C', $Root, 'remote', 'get-url', 'origin') -IgnoreExitCode
    }
    catch {
        return ''
    }

    if (-not $result -or $result.ExitCode -ne 0) { return '' }

    $line = @($result.StdOut -split "`n" | ForEach-Object { $_.Trim() } | Where-Object { $_ }) | Select-Object -First 1
    if ([string]::IsNullOrWhiteSpace($line)) { return '' }

    return ConvertTo-AvmManagedFilesRepoId -Name (Get-AvmRepoLeafFromUrl -Url $line)
}

function Get-AvmManagedFilesKnownRepoId {
    <#
    .SYNOPSIS
        Return the de-duplicated set of repository ids declared across every
        'repositoryGroups' entry of a parsed config.json.
    #>

    [CmdletBinding()]
    [OutputType([string[]])]
    param(
        [object] $RepositoryConfig
    )

    [string[]] $none = @()
    if (-not $RepositoryConfig) { return $none }
    if (-not ($RepositoryConfig.PSObject.Properties.Name -contains 'repositoryGroups')) { return $none }

    $ids = New-Object System.Collections.Generic.List[string]
    foreach ($group in @($RepositoryConfig.repositoryGroups)) {
        if (-not $group) { continue }
        if (-not ($group.PSObject.Properties.Name -contains 'repositories')) { continue }
        foreach ($repo in @($group.repositories)) {
            if (-not [string]::IsNullOrWhiteSpace($repo)) { $ids.Add([string]$repo) }
        }
    }

    [string[]] $unique = @($ids | Select-Object -Unique)
    return $unique
}

function Test-AvmManagedFilesInteractive {
    <#
    .SYNOPSIS
        Return whether the current host can prompt the user for a repository id.
        CI runs and redirected input are treated as non-interactive.
    #>

    [CmdletBinding()]
    [OutputType([bool])]
    param()

    try {
        if (-not [string]::IsNullOrEmpty($env:CI)) { return $false }
        if ([System.Console]::IsInputRedirected) { return $false }
    }
    catch {
        return $false
    }

    return $true
}

function Resolve-AvmManagedFilesRepoId {
    <#
    .SYNOPSIS
        Resolve the managed-files repository id using the F11/F100 resolution
        order: explicit value, matching git-origin candidate, matching folder-leaf
        candidate, unmatched git-origin fallback, unmatched folder-leaf fallback,
        interactive prompt, then a hard failure.

    .DESCRIPTION
        An explicit -RepoId (already carrying the -RepoId parameter, the
        AVM_MANAGED_FILES_REPO_ID environment value or a '.avm/managed-files.json'
        override) short-circuits the whole chain and is authoritative.

        Otherwise a candidate is derived from the git origin remote and from the
        working-tree folder leaf, each normalised by stripping a leading
        'terraform-azurerm-' / 'terraform-azapi-' prefix. A candidate matching
        config.json repositoryGroups membership is preferred so an overlay is not
        lost when only one candidate matches. If neither matches, the origin and
        folder candidates remain valid for root-only sync. An interactive host is
        prompted only when neither automatic candidate exists; resolution fails
        only when no repository id can be determined.
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory)]
        [string] $Root,

        [AllowEmptyString()]
        [string] $ExplicitRepoId = '',

        [string[]] $KnownRepoIds = @(),

        [string] $GitPath,

        [bool] $Interactive = $false
    )

    if (-not [string]::IsNullOrWhiteSpace($ExplicitRepoId)) {
        return $ExplicitRepoId.Trim()
    }

    $known = @($KnownRepoIds | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
    $originCandidate = Get-AvmManagedFilesOriginRepoId -Root $Root -GitPath $GitPath
    $rootLeaf = [System.IO.Path]::GetFileName([System.IO.Path]::TrimEndingDirectorySeparator($Root))
    $folderCandidate = ConvertTo-AvmManagedFilesRepoId -Name $rootLeaf

    if (-not [string]::IsNullOrWhiteSpace($originCandidate) -and ($known -contains $originCandidate)) {
        return $originCandidate
    }

    if (-not [string]::IsNullOrWhiteSpace($folderCandidate) -and ($known -contains $folderCandidate)) {
        return $folderCandidate
    }

    if (-not [string]::IsNullOrWhiteSpace($originCandidate)) {
        return $originCandidate
    }

    if (-not [string]::IsNullOrWhiteSpace($folderCandidate)) {
        return $folderCandidate
    }

    if ($Interactive) {
        $answer = Read-Host -Prompt 'Repository id could not be inferred. Enter the managed-files repository id'
        if (-not [string]::IsNullOrWhiteSpace($answer)) {
            $normalised = ConvertTo-AvmManagedFilesRepoId -Name $answer
            if (-not [string]::IsNullOrWhiteSpace($normalised)) { return $normalised }
        }
    }

    throw [AvmConfigurationException]::new(
        ("Could not resolve a managed-files repository id for '{0}' from its git origin or working-tree folder. " -f $Root) +
        "Set it explicitly with -RepoId, the AVM_MANAGED_FILES_REPO_ID environment variable, or a repoId in '.avm/managed-files.json'.")
}

function Set-AvmManagedFileExecutableBit {
    <#
    .SYNOPSIS
        Set the owner/group/other execute bits on a synced file's working-tree
        entry without ever touching the git index (F13). No-op on Windows.
    #>

    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
        'PSUseShouldProcessForStateChangingFunctions', '',
        Justification = 'Working-tree file-mode repair; the caller already gates writes via ShouldProcess.')]
    param(
        [Parameter(Mandatory)]
        [string] $Path
    )

    if ($IsWindows) { return }
    if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { return }

    try {
        $item = Get-Item -LiteralPath $Path -Force
        $execute = [System.IO.UnixFileMode]::UserExecute -bor [System.IO.UnixFileMode]::GroupExecute -bor [System.IO.UnixFileMode]::OtherExecute
        $item.UnixFileMode = $item.UnixFileMode -bor $execute
    }
    catch {
        Write-AvmLog "Failed to set executable bit on '$Path': $($_.Exception.Message)" -Level Verbose
    }
}

function Get-AvmManagedFilesFileConfig {
    <#
    .SYNOPSIS
        Read the optional '.avm/managed-files.json' override file from a repo
        working tree, returning an empty hashtable when it is absent or invalid.
    #>

    [CmdletBinding()]
    [OutputType([hashtable])]
    param(
        [Parameter(Mandatory)]
        [string] $Root
    )

    $result = @{}
    $configPath = Join-Path (Join-Path $Root '.avm') 'managed-files.json'
    if (-not (Test-Path -LiteralPath $configPath -PathType Leaf)) { return $result }

    try {
        $json = Get-Content -LiteralPath $configPath -Raw | ConvertFrom-Json
    }
    catch {
        Write-AvmLog "Failed to parse '$configPath': $($_.Exception.Message)" -Level Warning
        return $result
    }

    foreach ($property in $json.PSObject.Properties) {
        $result[$property.Name] = $property.Value
    }
    return $result
}

function Resolve-AvmManagedFilesSource {
    <#
    .SYNOPSIS
        Resolve the managed-files base directory (and optional config folder),
        fetching the source git repo into the AVM cache unless a local path
        override is supplied.
    #>

    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
        'PSUseShouldProcessForStateChangingFunctions', '',
        Justification = 'Prepares a local checkout for read-only consumption; performs no destructive state change.')]
    [OutputType([hashtable])]
    param(
        [Parameter(Mandatory)]
        [hashtable] $Settings,

        [string] $GitPath
    )

    if ($Settings.ManagedFilesLocalPath) {
        if (-not (Test-Path -LiteralPath $Settings.ManagedFilesLocalPath -PathType Container)) {
            throw [System.IO.DirectoryNotFoundException]::new(
                "Managed-files local path not found: $($Settings.ManagedFilesLocalPath)")
        }
        $baseDir = (Resolve-Path -LiteralPath $Settings.ManagedFilesLocalPath).Path

        $configDir = $null
        if ($Settings.ConfigLocalPath -and (Test-Path -LiteralPath $Settings.ConfigLocalPath -PathType Container)) {
            $configDir = (Resolve-Path -LiteralPath $Settings.ConfigLocalPath).Path
        }

        return @{
            ManagedBaseDir = $baseDir
            ConfigDir      = $configDir
            SourceKind     = 'local'
            ToolPath       = $baseDir
        }
    }

    if (-not $GitPath) {
        throw [System.InvalidOperationException]::new(
            "git is required to fetch managed files from '$($Settings.ManagedFilesRepo)' but was not found on PATH. Provide -ManagedFilesLocalPath to use a local source instead.")
    }

    $checkout = Get-AvmManagedFilesCheckout -Repo $Settings.ManagedFilesRepo -Ref $Settings.ManagedFilesRef -GitPath $GitPath
    $baseDir = Join-Path $checkout $Settings.ManagedFilesPath

    $configDir = $null
    if ($Settings.ConfigLocalPath -and (Test-Path -LiteralPath $Settings.ConfigLocalPath -PathType Container)) {
        $configDir = (Resolve-Path -LiteralPath $Settings.ConfigLocalPath).Path
    }
    elseif ($Settings.ConfigRepo -eq $Settings.ManagedFilesRepo -and $Settings.ConfigRef -eq $Settings.ManagedFilesRef) {
        $configDir = Join-Path $checkout $Settings.ConfigPath
    }
    else {
        $configCheckout = Get-AvmManagedFilesCheckout -Repo $Settings.ConfigRepo -Ref $Settings.ConfigRef -GitPath $GitPath
        $configDir = Join-Path $configCheckout $Settings.ConfigPath
    }

    return @{
        ManagedBaseDir = $baseDir
        ConfigDir      = $configDir
        SourceKind     = 'governance'
        ToolPath       = $baseDir
    }
}

function Get-AvmManagedFilesCheckout {
    <#
    .SYNOPSIS
        Shallow clone (or fetch, when already cached) a git repo at a ref into
        the AVM cache and return the checkout root.
    #>

    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
        'PSUseShouldProcessForStateChangingFunctions', '',
        Justification = 'Populates a private cache directory used read-only by the caller; not a user-facing state change.')]
    [OutputType([string])]
    param(
        [Parameter(Mandatory)]
        [string] $Repo,

        [Parameter(Mandatory)]
        [string] $Ref,

        [Parameter(Mandatory)]
        [string] $GitPath
    )

    $homeDir = if ($env:AVM_HOME) { $env:AVM_HOME } else { Join-Path ([System.IO.Path]::GetTempPath()) 'avm' }
    $slug = $Repo -replace '[\\/]', '_'
    $cacheRoot = Join-Path (Join-Path (Join-Path (Join-Path $homeDir 'cache') 'managed-files') $slug) $Ref

    if (Test-Path -LiteralPath (Join-Path $cacheRoot '.git') -PathType Container) {
        Invoke-AvmProcess -FilePath $GitPath -ArgumentList @('-C', $cacheRoot, 'fetch', '--depth', '1', 'origin', $Ref) | Out-Null
        Invoke-AvmProcess -FilePath $GitPath -ArgumentList @('-C', $cacheRoot, 'checkout', '-q', 'FETCH_HEAD') | Out-Null
    }
    else {
        $parent = Split-Path -Parent $cacheRoot
        if ($parent -and -not (Test-Path -LiteralPath $parent)) {
            New-Item -ItemType Directory -Path $parent -Force | Out-Null
        }
        if (Test-Path -LiteralPath $cacheRoot) {
            Remove-Item -LiteralPath $cacheRoot -Recurse -Force
        }
        Invoke-AvmProcess -FilePath $GitPath -ArgumentList @('clone', '--depth', '1', '--branch', $Ref, "https://github.com/$Repo.git", $cacheRoot) | Out-Null
    }

    return $cacheRoot
}

function Get-AvmGitIndexMode {
    <#
    .SYNOPSIS
        Read git tree-entry modes ('100644' / '100755') from a directory's git
        index, keyed by forward-slash relative path. Returns an empty map when
        git is unavailable or the directory is not a working tree.
    #>

    [CmdletBinding()]
    [OutputType([hashtable])]
    param(
        [Parameter(Mandatory)]
        [string] $Dir,

        [string] $GitPath
    )

    $modeMap = @{}
    if (-not $GitPath) { return $modeMap }
    if (-not (Test-Path -LiteralPath $Dir -PathType Container)) { return $modeMap }

    $result = $null
    try {
        $result = Invoke-AvmProcess -FilePath $GitPath -ArgumentList @('-C', $Dir, 'ls-files', '--stage', '--', '.') -IgnoreExitCode
    }
    catch {
        return $modeMap
    }

    if ($result.ExitCode -ne 0 -or [string]::IsNullOrEmpty($result.StdOut)) { return $modeMap }

    foreach ($line in ($result.StdOut -split "`n")) {
        if ($line -match '^(\d{6})\s+[0-9a-f]+\s+\d+\t(.+)$') {
            $modeMap[($matches[2] -replace '\\', '/')] = $matches[1]
        }
    }
    return $modeMap
}

function Resolve-AvmManagedFileTargetPath {
    <#
    .SYNOPSIS
        Resolve a managed source path to its concrete repository target paths.

    .DESCRIPTION
        Paths below a reserved '<parent>/_all/' source subtree are broadcast
        into each existing immediate child directory of the corresponding
        target parent. Multiple reserved segments are expanded from left to
        right. A root-level '_all/' and all other paths are returned unchanged.
    #>

    [CmdletBinding()]
    [OutputType([string[]])]
    param(
        [Parameter(Mandatory)]
        [string] $RelativePath,

        [Parameter(Mandatory)]
        [string] $TargetRoot
    )

    $broadcast = [System.Text.RegularExpressions.Regex]::Match(
        $RelativePath,
        '^(.+?)/_all/(.+)$',
        [System.Text.RegularExpressions.RegexOptions]::CultureInvariant)
    if (-not $broadcast.Success) {
        return $RelativePath
    }

    $parentPath = $broadcast.Groups[1].Value
    $suffix = $broadcast.Groups[2].Value
    $parentRoot = Join-Path $TargetRoot ($parentPath.Replace('/', [System.IO.Path]::DirectorySeparatorChar))
    if (-not (Test-Path -LiteralPath $parentRoot -PathType Container)) {
        return
    }

    Get-ChildItem -LiteralPath $parentRoot -Directory -Force |
        Where-Object { $_.Name -ne '_all' } |
        Sort-Object -Property Name |
        ForEach-Object {
            $expandedPath = "$parentPath/$($_.Name)/$suffix"
            Resolve-AvmManagedFileTargetPath -RelativePath $expandedPath -TargetRoot $TargetRoot
        }
}

function Add-AvmManagedFilesFromDir {
    <#
    .SYNOPSIS
        Add every file under a base directory to a managed-files map keyed by
        forward-slash relative path, capturing the source path and git index
        mode. Dotfiles are included; '.gitkeep' placeholders are not.
    #>

    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
        'PSUseShouldProcessForStateChangingFunctions', '',
        Justification = 'Populates a caller-owned hashtable; performs no external state change.')]
    param(
        [string] $BaseDir,

        [Parameter(Mandatory)]
        [hashtable] $Map,

        [Parameter(Mandatory)]
        [string] $TargetRoot,

        [string] $GitPath
    )

    if ([string]::IsNullOrEmpty($BaseDir)) { return }
    if (-not (Test-Path -LiteralPath $BaseDir -PathType Container)) {
        Write-AvmLog "Managed files directory does not exist: $BaseDir" -Level Warning
        return
    }

    # Get-Item (not Resolve-Path) so the prefix matches Get-ChildItem FullName:
    # Resolve-Path preserves 8.3 short components, Get-ChildItem expands them.
    $baseDirAbsolute = (Get-Item -LiteralPath $BaseDir -Force).FullName
    $modeMap = Get-AvmGitIndexMode -Dir $baseDirAbsolute -GitPath $GitPath

    $lineSpecName = Get-AvmManagedLineSpecFileName

    # -Force: dotfiles are hidden on Linux/macOS and would be skipped silently.
    $sourceFiles = @(
        Get-ChildItem -LiteralPath $baseDirAbsolute -Recurse -File -Force | Where-Object {
            # '.gitkeep' files exist only to keep otherwise-empty overlay
            # directories tracked in git. They are placeholders, never real managed
            # content, so they must not be synced into target repos. The upstream
            # avm-terraform-governance sync applies the same filter; omitting it
            # here would make every drift check demand a file the sync never writes.
            # The line-managed-file spec is tooling metadata consumed separately, so
            # it is filtered here for the same reason.
            $_.Name -ne '.gitkeep' -and $_.Name -ne $lineSpecName
        } | ForEach-Object {
            $relativePath = [System.IO.Path]::GetRelativePath($baseDirAbsolute, $_.FullName) -replace '\\', '/'
            $mode = $modeMap[$relativePath]
            if (-not $mode) { $mode = '100644' }
            [pscustomobject]@{
                RelativePath = $relativePath
                Source       = $_.FullName -replace '\\', '/'
                Mode         = $mode
                IsBroadcast  = $relativePath -cmatch '^.+?/_all/.+'
            }
        }
    )

    # Broadcast templates are applied before literal paths from the same source,
    # so a concrete path remains the more-specific override. Build calls this
    # helper once per source in precedence order, preserving overlay wins.
    $broadcastFiles = @($sourceFiles | Where-Object { $_.IsBroadcast } | Sort-Object -Property RelativePath)
    $literalFiles = @($sourceFiles | Where-Object { -not $_.IsBroadcast } | Sort-Object -Property RelativePath)
    foreach ($sourceFile in @($broadcastFiles + $literalFiles)) {
        foreach ($targetPath in @(Resolve-AvmManagedFileTargetPath -RelativePath $sourceFile.RelativePath -TargetRoot $TargetRoot)) {
            $Map[$targetPath] = @{
                Source = $sourceFile.Source
                Mode   = $sourceFile.Mode
            }
        }
    }
}

function Build-AvmManagedFilesMap {
    <#
    .SYNOPSIS
        Build the managed-files map from '<base>/root' plus zero or more
        overlays, minus any excluded paths.

    .DESCRIPTION
        Overlays are applied in the order supplied, after 'root'. A file
        present in more than one source is taken from the last source that
        declares it, so later overlays win over earlier ones and every overlay
        wins over 'root'.
    #>

    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
        'PSUseShouldProcessForStateChangingFunctions', '',
        Justification = 'Factory function; returns a new hashtable and mutates no external state.')]
    [OutputType([hashtable])]
    param(
        [Parameter(Mandatory)]
        [string] $BaseDir,

        [Parameter(Mandatory)]
        [string] $TargetRoot,

        [string[]] $Overlays = @(),

        [string[]] $Excluded = @(),

        [string] $RepoId,

        [string] $GitPath
    )

    $rootDir = Join-Path $BaseDir 'root'

    $map = @{}
    Add-AvmManagedFilesFromDir -BaseDir $rootDir -Map $map -TargetRoot $TargetRoot -GitPath $GitPath
    foreach ($overlay in $Overlays) {
        if ([string]::IsNullOrWhiteSpace($overlay)) { continue }
        Add-AvmManagedFilesFromDir -BaseDir (Join-Path $BaseDir $overlay) -Map $map -TargetRoot $TargetRoot -GitPath $GitPath
    }

    foreach ($excludedPath in $Excluded) {
        foreach ($resolvedExcludedPath in @(Resolve-AvmManagedFileTargetPath -RelativePath $excludedPath -TargetRoot $TargetRoot)) {
            if ($map.ContainsKey($resolvedExcludedPath)) {
                $map.Remove($resolvedExcludedPath) | Out-Null
                Write-AvmLog "Excluded managed file from sync: $resolvedExcludedPath" -Level Verbose
            }
        }
    }

    Write-AvmLog "Resolved $($map.Count) managed file(s) for repository '$RepoId' (overlays='$($Overlays -join ', ')', exclusions=$($Excluded.Count))." -Level Verbose
    return $map
}

function Resolve-AvmManagedFilesRepositorySetting {
    <#
    .SYNOPSIS
        Resolve the per-repository managed-files overlays and exclusions from a
        parsed config.json by matching the repository id against
        'repositoryGroups'.

    .DESCRIPTION
        A repository may belong to several groups that each declare a
        'managedFilesAdditional' overlay. All of them apply, stacked so that a
        later overlay's files win over an earlier one's.

        Stacking order is explicit: each group may carry an integer
        'managedFilesOrder' (default 0). Overlays sort by that value ascending,
        with ties broken by declaration order in config.json. A lower order is
        applied earlier and therefore *loses* to a higher order. Relying on
        declaration order alone was fragile - reordering config.json for tidiness
        silently changed precedence.

        This ordering must stay identical to Get-RepositorySettings in
        tf-repo-mgmt/scripts/lib/RepositoryConfig.ps1 in avm-terraform-governance,
        or 'avm pr-check' drift detection will disagree with what the sync
        pipeline actually writes.
    #>

    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
        'PSUseShouldProcessForStateChangingFunctions', '',
        Justification = 'Factory function; returns a settings hashtable and mutates no external state.')]
    [OutputType([hashtable])]
    param(
        [Parameter(Mandatory)]
        [object] $RepositoryConfig,

        [Parameter(Mandatory)]
        [string] $RepoId
    )

    $repositoryGroups = @()
    if ($RepositoryConfig.PSObject.Properties.Name -contains 'repositoryGroups' -and $RepositoryConfig.repositoryGroups) {
        $repositoryGroups = @($RepositoryConfig.repositoryGroups | Where-Object { $_.repositories -contains $RepoId })
    }

    $overlayEntries = @()
    $declarationIndex = 0
    foreach ($repositoryGroup in $repositoryGroups) {
        if ($repositoryGroup.PSObject.Properties.Name -contains 'managedFilesAdditional' -and $repositoryGroup.managedFilesAdditional) {
            $order = 0
            if ($repositoryGroup.PSObject.Properties.Name -contains 'managedFilesOrder' -and $null -ne $repositoryGroup.managedFilesOrder) {
                $order = [int] $repositoryGroup.managedFilesOrder
            }
            foreach ($overlay in @($repositoryGroup.managedFilesAdditional)) {
                $overlayEntries += [pscustomobject]@{
                    Overlay = $overlay
                    Order   = $order
                    Index   = $declarationIndex
                }
            }
        }
        $declarationIndex++
    }

    $managedFilesAdditional = @(
        $overlayEntries |
            Sort-Object -Property Order, Index |
            Select-Object -ExpandProperty Overlay |
            Select-Object -Unique
    )

    $excludedManagedFiles = @()
    foreach ($repositoryGroup in $repositoryGroups) {
        if ($repositoryGroup.PSObject.Properties.Name -contains 'excludedManagedFiles' -and $repositoryGroup.excludedManagedFiles) {
            $excludedManagedFiles += @($repositoryGroup.excludedManagedFiles)
        }
    }
    $excludedManagedFiles = @($excludedManagedFiles | Select-Object -Unique)

    return @{
        ManagedFilesAdditional = $managedFilesAdditional
        ExcludedManagedFiles   = $excludedManagedFiles
    }
}

function Get-AvmMatchingDeprecatedPath {
    <#
    .SYNOPSIS
        Return the subset of deprecated candidate paths that are present on
        disk under a repository root, matching either an exact file or a
        directory.
    #>

    [CmdletBinding()]
    [OutputType([string[]])]
    param(
        [string[]] $CandidatePaths = @(),

        [Parameter(Mandatory)]
        [string] $Root
    )

    $matched = @()
    foreach ($candidate in $CandidatePaths) {
        if ([string]::IsNullOrEmpty($candidate)) { continue }
        $full = Join-Path $Root ($candidate.Replace('/', [System.IO.Path]::DirectorySeparatorChar))
        if (Test-Path -LiteralPath $full) {
            $matched += $candidate
        }
    }
    return $matched
}

function Get-AvmGitBlobSha {
    <#
    .SYNOPSIS
        Compute git's blob SHA-1 for the given content bytes in-process:
        sha1("blob " + length + "\0" + content).
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param(
        [byte[]] $Bytes
    )

    if ($null -eq $Bytes) { $Bytes = New-Object byte[] 0 }

    $header = [System.Text.Encoding]::ASCII.GetBytes("blob $($Bytes.Length)`0")
    $combined = New-Object byte[] ($header.Length + $Bytes.Length)
    [System.Array]::Copy($header, 0, $combined, 0, $header.Length)
    [System.Array]::Copy($Bytes, 0, $combined, $header.Length, $Bytes.Length)

    $sha1 = [System.Security.Cryptography.SHA1]::Create()
    try {
        $hashBytes = $sha1.ComputeHash($combined)
        return ([System.BitConverter]::ToString($hashBytes) -replace '-', '').ToLowerInvariant()
    }
    finally {
        $sha1.Dispose()
    }
}

function Get-AvmDesiredManagedFile {
    <#
    .SYNOPSIS
        Build the desired managed-file set as { path -> @{ Bytes; Sha; Mode } }
        by reading each source file's bytes and computing its git blob SHA.
    #>

    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
        'PSUseShouldProcessForStateChangingFunctions', '',
        Justification = 'Factory function; returns a new hashtable and mutates no external state.')]
    [OutputType([hashtable])]
    param(
        [Parameter(Mandatory)]
        [hashtable] $ManagedFiles
    )

    $desired = @{}
    foreach ($targetPath in $ManagedFiles.Keys) {
        $entry = $ManagedFiles[$targetPath]
        $sourcePath = $entry.Source
        $mode = $entry.Mode
        if (-not $mode) { $mode = '100644' }
        if (-not (Test-Path -LiteralPath $sourcePath -PathType Leaf)) {
            Write-AvmLog "Managed file source missing on disk: $sourcePath (target=$targetPath)" -Level Warning
            continue
        }
        $bytes = [System.IO.File]::ReadAllBytes($sourcePath)
        $desired[$targetPath] = @{
            Bytes = $bytes
            Sha   = Get-AvmGitBlobSha -Bytes $bytes
            Mode  = $mode
        }
    }
    return $desired
}

function New-AvmSyncIssue {
    <#
    .SYNOPSIS
        Build a shared-shape Issue object for the managed-files sync engine.
    #>

    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
        'PSUseShouldProcessForStateChangingFunctions', '',
        Justification = 'Factory function; returns a new pscustomobject and mutates no external state.')]
    [OutputType([pscustomobject])]
    param(
        [Parameter(Mandatory)]
        [string] $File,

        [Parameter(Mandatory)]
        [string] $Message
    )

    [pscustomobject][ordered]@{
        File     = $File
        Line     = 0
        Column   = 0
        Severity = 'error'
        Code     = ''
        Message  = $Message
    }
}

# SIG # Begin signature block
# MIInNwYJKoZIhvcNAQcCoIInKDCCJyQCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDWhSQLlMJSmi5W
# lOTRp7NTt7lJSwivIyLgP4Lt/7Zr+6CCDMkwggYEMIID7KADAgECAhMzAAACHPrN
# 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/AUmQN5W4n101cY2L4A7GTQG1h32HHAvfQESWP0xghnEMIIZwAIBATBu
# MFcxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
# KDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMjQCEzMAAAIc
# +s3Fm+gvfsQAAAAAAhwwDQYJYIZIAWUDBAIBBQCggZAwGQYJKoZIhvcNAQkDMQwG
# CisGAQQBgjcCAQQwLwYJKoZIhvcNAQkEMSIEIA4Mg776NgA3qmO6vQbv4d+o0mbB
# MHos4GGyisbCZDBrMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8AcwBv
# AGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEBBQAE
# ggEAoySptMPUh7wKsOZeC+dQPnSLaj5pNiK2jP9pClApCtMC1dtP+WiDQw1EsU0O
# MRCy3h0qn6lIeMSVxA3blGQYZXXHm91KQ+NiuRUbGrZQvrHcw+CRCwc0k6J6sJ8b
# L8CZ9nvd0S1oZq0Cfw8cOhWQoFC8TtfCaORpUDeD30hExvBNYhFl2LQRzy9LRkrW
# feQ8JNUVn0opSH3tssg7962GWOpx0vqUH2+/9Vv+EOQgBa28UmO3qlptCotlrQKQ
# LPaOh9wwTl8y2fPJcAIPrqvXZdqCvfhwVne8brRahQypi/llSCZIF0YuKUoUdkSm
# Y+u8j8jTSICF5MO+yKGoJKoPB6GCF5QwgheQBgorBgEEAYI3AwMBMYIXgDCCF3wG
# CSqGSIb3DQEHAqCCF20wghdpAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsqhkiG
# 9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFlAwQC
# AQUABCAVYFQDdWBuf9sbnObapEi94Rh3zFs5zBYlq8uAGCPXNwIGal+WJnpUGBMy
# MDI2MDgxMDE1MjEwOS4xNDFaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJVUzET
# MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV
# TWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmlj
# YSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046RTAwMi0wNUUw
# LUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2WgghHq
# MIIHIDCCBQigAwIBAgITMwAAAikO1WQqtJfyGgABAAACKTANBgkqhkiG9w0BAQsF
# ADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yNjAyMTkxOTQwMDda
# Fw0yNzA1MTcxOTQwMDdaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu
# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv
# cmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMScw
# JQYDVQQLEx5uU2hpZWxkIFRTUyBFU046RTAwMi0wNUUwLUQ5NDcxJTAjBgNVBAMT
# HE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEBAQUA
# A4ICDwAwggIKAoICAQCeItFq4z1oCYSmUZmpYDsbJWEu++1bbc/Mz7Pa3I0ZX5EO
# N+WirB0FvnGlyFRUylzO5TJXZfU8QFPOU95P1Y1OZ8J+quA5G+AWSBOr/48scl0s
# 9RBpqgTMq/lbyqBz4CMmvVR2QevAgVp4a1hbmOm9G7YWey68N5F5rSDYV0wMlg4I
# y8YRuFgRN2eBpVXt9IvFaFmBnQLZfo22KZ3L8PWEHUhXU5dLOSZoTfqqQ/B+deW5
# 6ACMnnHjPxZu+szHhZMLUrMWTgs9J7Cn8DtelcKj9aM+0Zq7tkSDHCrwo6eCSfw3
# clktXRRrdmsccal8RCDiNFFgZsypwF2aGAF6kg41+Ql+thXpnOMUH4mPCAJZWp0z
# DWowsK/Yo5jHL1pT/AgbL3FoAy4cbhOI4Pb1eQFG+jT7skS2F/b+ZACUA1EDZ830
# K+Bu0yw+FpSGy8tpd1szk3cUYjIpzIG4z3oFNmiSJN8YdNd4SHsER5Dks5bxiKbp
# vmfrOA39jTb7EW2TT7ySWgJISfvTezuLmQsTVSzNsvapVlHhE2zBqDw409nvOtit
# CFbnhhXNfatzb2+Gf2tX2s6YBa151CC/8+emJvvegXbWNudzYt8cFRom0PZ+fJRh
# hBfdSqCqr8QeOGJ8VYlmxFXqx1SdDSkTCSgpsskGqZwh/6umA1g4L7zeGBNngQID
# AQABo4IBSTCCAUUwHQYDVR0OBBYEFCdNRaSL9AW8QvaQ21WjRAXKN4M7MB8GA1Ud
# IwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCGTmh0
# dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUyMFRp
# bWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4wXAYI
# KwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMv
# TWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwGA1Ud
# EwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQDAgeA
# MA0GCSqGSIb3DQEBCwUAA4ICAQA9wc72lf/czDhp09T3PGAMOQhxl/x04jpE7t39
# FeqQSn2Up6DVzhgwnzCqY3NIhLtUaWrd7NxvrhZDca+J4xzvrRQNPHeRQpnJVeHs
# yTu53gTBlUB1TRI6OnZt/AVmR9oMJ/NBOqB+d+SOb8Px6zRgRwk62sFkOkB5lig/
# DMnYEeR/amW9Hdo8vXcKmaa/DbSOAHSdfZFt+iqMZfNlkEOn71/RAKTNv4Qpq/2F
# hcjMMmSkIhshBdBVB0VjmkwFfhVUf5TTuLJ9sDR4EyCvOZJ3B6g7Iw6WjQxycjwk
# fzsVMTpfusJ5SwdOHL8yGPWZOePjwa8ISXWs6kiVK/6S0/JVb1LpxpyYKREQjnU/
# 5OecKt2OXlHdwFWZrwAi98RPZa6EExcb/LGLf10tNHju1eTlohY0jzNZQ0BDgSuM
# ZgMU+8EEjtMQMIDnlPGEUON7LHXHH0KL0FA01PEWVZKrr/LUOuuDTNFzw543FPMp
# 4gkCIFlKdRuciR1IXOk+Xse6rj9tJFYgVn+44BHou2XQe5RX30ef3AQWa0mxyGDq
# JzGsV3X5+bNQeMV88iWulJPq5sgnGG9O/H1/HH4HsO9ZKGX/WrJpQmFuQrTOR49X
# jveaC0xaFmGsNg+RhbtD5qTkn+ISDvw0IJ/E/VXNdz/yWgol6r507hT8sAMupnhk
# F2uw1DCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZIhvcN
# AQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD
# VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAw
# BgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEw
# MB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt
# U3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDk
# 4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25PhdgM/9c
# T8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPFdvWG
# UNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6Gnsz
# rYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBpDco2
# LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50ZuyjLV
# wIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3EXzTd
# EonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0lBw0
# gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1qGFph
# AXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ+QuJ
# YfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PAPBXb
# GjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkwEgYJ
# KwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxGNSnP
# EP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARVMFMw
# UQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWljcm9z
# b2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAKBggr
# BgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYw
# DwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoY
# xDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtp
# L2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYB
# BQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20v
# cGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG9w0B
# AQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0xM7U5
# 18JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmCVgAD
# sAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449xvNo
# 32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wMnosZ
# iefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDSPeZK
# PmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2dY3RI
# LLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxnGSgk
# ujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+CrvsQWY9
# af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokLjzba
# ukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL6Xu/
# OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNNMIIC
# NQIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0
# b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh
# dGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEnMCUG
# A1UECxMeblNoaWVsZCBUU1MgRVNOOkUwMDItMDVFMC1EOTQ3MSUwIwYDVQQDExxN
# aWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQC3v9iS
# O22xob7ZxN5dXCEq+9Iv/6CBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
# EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv
# ZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBD
# QSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA7iPHFjAiGA8yMDI2MDgxMDAzNDIxNFoY
# DzIwMjYwODExMDM0MjE0WjB0MDoGCisGAQQBhFkKBAExLDAqMAoCBQDuI8cWAgEA
# MAcCAQACAihTMAcCAQACAhL6MAoCBQDuJRiWAgEAMDYGCisGAQQBhFkKBAIxKDAm
# MAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZIhvcN
# AQELBQADggEBAMVo/TOQV3HVyZFAaHMeg2EKO23oAgrnwXDCqLu/rYDf2hhMwNkH
# 6NRMu+OfZ1s4YRTxsYe6PowFScm2X1ZHr2LzIqH4DSOlRa5SyB8iH2LHIszV72Db
# mAlQn5NOI2MNdDVRqCU3x65pI0MLxVmImzR4xXSaZ/gZ3g3/4yTTFNDG9O9BhZDk
# uIUL8vCe1j+ABJSPkG+LYpnIxBTep5NL/6bnWiignnd2V26qRCl6DMVK6pMu3ohe
# +WCQxfT2OrCP/ivznlAcPSrPKdqfQyj35BQgSAHcf/Gum8ls9w1x9yO5KE5Q1BaM
# zNzjfLddoPUjbBDjrPsahU6Z1DpLkbgQuCsxggQNMIIECQIBATCBkzB8MQswCQYD
# VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEe
# MBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3Nv
# ZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAikO1WQqtJfyGgABAAACKTANBglg
# hkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqG
# SIb3DQEJBDEiBCDKSJpasIc3DUY1e2TDD4pdhakR1p7TJxk9EXqOm6pGVzCB+gYL
# KoZIhvcNAQkQAi8xgeowgecwgeQwgb0EILfKPfEitvD/lSvEumxqPkkeOEtgkmKF
# EVMuel9oOrqSMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAC
# EzMAAAIpDtVkKrSX8hoAAQAAAikwIgQgMWDzRc/Rqc3iFK8Xjn7OAPL0GW1ULV9Z
# 0YW33FWO7fgwDQYJKoZIhvcNAQELBQAEggIAdulw8aNp/rZRSqzlwBV4qN2CipwS
# H/4bOjyjI739PqZsdbQBH0AhNhq+ERwycHmsk1xUL4SLryLDDBiEqAoL6SDdpIUd
# cagmza2ac5rr2/vMyVmztCi7lwnl1b9fAWRU0lf+M5ljmFLh6LRF7I3Ib5fxmFr+
# 1vNUMDSTksPSUOMK9dSw6+nLMnLMhgqxCY797huHzv708ovg1h5o+Zjutp+rMAKn
# ne/G/G6vXjspM+X22Wx3BEGGhq/Yjt+VyNT5pKMwiT8cGpKNMq2Bpn1KINSAahXf
# kNWKYOJuiD/cyXBxrr197Q5a68ZbSwMhCG2QMnJ6OBZlOSL1hE2WAH6K8jTwt73B
# EfKyiOHs8vGotCVaQyDI4B/Z9k3Fsx1NSt1X7UnZ09ZhRx07l+1gMQB26VdEOh8G
# L9egLZuvCwysiFJz2GcM8xKf6N9bv/mmFlqYvAcP0onGABWwVHHUVh2cs6YXfiR5
# /t1704OaTX12tJhDAP+wHtbYHhNQgXJH3XglDI/Glw1YYBHldbKFyzmgL2W7nIFM
# 8D5NmwLshM3LDcXwv8f4OwI0f9T1Gtj4HFUX5V46m4pIDuRAlzJ1Mr7ZNLe/G1XA
# UKwd8xAE11KBc+/9WenbjFVcXX40y2JnP+RuyFZpVpbl5ejiHN/4XD+WcD5EO2Ps
# o0LiY6YbWeLuGkE=
# SIG # End signature block