Modules/businessdev.ALbuild.Containers/Private/Test-BcArtifactCacheUsable.ps1

function Test-BcArtifactCacheUsable {
    <#
    .SYNOPSIS
        Answers whether a cached artifact folder can be reused - the marker is there AND the content it
        recorded is still there.
 
    .DESCRIPTION
        Get-BcArtifact writes a completion marker carrying a FINGERPRINT (file count and total bytes),
        for the express purpose of noticing later that content went missing. On the path that actually
        matters that fingerprint was never read: three separate guards asked only whether the marker
        FILE EXISTS, so the protection was dead code.
 
        Measured on this estate, not hypothesised: the platform artifacts of BC 25.18, 26.16 and 27.10
        each held ~0.7 GB where their own markers recorded 1.89 GB and 4322 files. Missing were
        ModernDev, Applications and ConfigurationPackages - and with ModernDev goes System.app, the
        symbol every compile needs. Both routes then failed a long way from the cause: the container
        with "Cannot bind argument to parameter 'Path' because it is null", the source engine with
        "A package with publisher 'Microsoft', name 'System' ... could not be found". A forced
        re-download restored 4323 files / 1.76 GB from the same URL, so the content was recoverable the
        whole time - nothing asked for it.
 
        LOSS is the test, not inequality. An older ALbuild staged first-party symbols into the artifact
        folder and fingerprinted that, so a folder can legitimately be BIGGER than its record;
        re-downloading multiple GB over a surplus would be a regression of its own. Only a shortfall
        means content is gone.
 
    .PARAMETER Path
        The cached artifact folder (a country folder or a platform folder).
 
    .OUTPUTS
        PSCustomObject with Usable (bool) and Reason (string, '' when usable).
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $Path
    )

    $marker = Join-Path $Path '.albuild-complete'
    if (-not (Test-Path -LiteralPath $marker)) {
        return [PSCustomObject]@{ Usable = $false; Reason = 'no completion marker' }
    }

    $recorded = $null
    try { $recorded = Get-Content -LiteralPath $marker -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop }
    catch { $recorded = $null }

    # A legacy bare-timestamp marker carries nothing to check against; it is still the statement that
    # the extraction finished, which is all it ever was.
    $hasFingerprint = $recorded -and
        ($recorded.PSObject.Properties.Name -contains 'bytes') -and
        ($recorded.PSObject.Properties.Name -contains 'files')
    if (-not $hasFingerprint) {
        return [PSCustomObject]@{ Usable = $true; Reason = '' }
    }

    $now = Get-BcArtifactInventory -Path $Path
    if ([long]$recorded.bytes -le $now.Bytes) {
        return [PSCustomObject]@{ Usable = $true; Reason = '' }
    }

    $gb = { param($b) [Math]::Round([double]$b / 1GB, 2) }
    return [PSCustomObject]@{
        Usable = $false
        Reason = ("content has gone missing since it was cached - recorded $($recorded.files) file(s) / " +
            "$(& $gb ([long]$recorded.bytes)) GB, found $($now.Files) / $(& $gb $now.Bytes) GB")
    }
}