Private/Wait-JIMActivityCompletion.ps1

# Copyright (c) Tetron Limited. All rights reserved.
# Licensed under the Tetron Commercial License. See LICENSE file in the project root.

function Wait-JIMActivityCompletion {
    <#
    .SYNOPSIS
        Blocks until a JIM Activity reaches a terminal status, rendering live progress.
 
    .DESCRIPTION
        Polls /api/v1/activities/{id}/progress (the lightweight endpoint: status, counts, phase
        message, throughput and ETA, without the cost of the full detail read) until the Activity
        reaches one of the terminal statuses, then returns that status so the caller can decide what
        a failure means in its own terms.
 
        A transient polling failure is not fatal: it is warned about and the wait continues, because an
        Activity that is still running outlives a dropped request. Repeated authentication failures are,
        because every further poll would fail the same way; the caller is told the operation is still
        running on the server and how to check on it once re-authenticated.
 
    .PARAMETER ActivityId
        The Activity to wait for.
 
    .PARAMETER ActivityLabel
        The Write-Progress -Activity label, e.g. "Recalling contributed values".
 
    .PARAMETER Timeout
        Maximum seconds to wait. Omit to wait indefinitely.
 
    .PARAMETER PollIntervalSeconds
        Seconds between polls. Defaults to 2.
 
    .PARAMETER AbortSentinelPath
        Optional path to a cooperative abort sentinel. When the file exists and is non-empty, a test
        harness has decided the run should fail (typically because an error watcher saw an [ERR] line in
        JIM's logs), and the wait throws rather than polling to its natural end.
 
    .OUTPUTS
        The Activity's terminal status as a string, or $null if the timeout elapsed first.
    #>

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

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

        [ValidateRange(1, [int]::MaxValue)]
        [int]$Timeout,

        [ValidateRange(1, [int]::MaxValue)]
        [int]$PollIntervalSeconds = 2,

        [string]$AbortSentinelPath
    )

    # Matches the ActivityStatus enum names the progress endpoint returns.
    $terminalStatuses = @('Complete', 'CompleteWithWarning', 'CompleteWithError', 'FailedWithError', 'Cancelled')

    $hasTimeout = $PSBoundParameters.ContainsKey('Timeout')
    $startTime = Get-Date
    $lastStatus = ''

    # Invoke-JIMApi may have refreshed the token transparently, so a single authentication failure is
    # worth another attempt; three in a row is not.
    $consecutiveAuthFailures = 0
    $maxAuthFailures = 3

    while ($true) {
        if ($AbortSentinelPath -and (Test-Path $AbortSentinelPath)) {
            $sentinelInfo = Get-Item $AbortSentinelPath -ErrorAction SilentlyContinue
            if ($sentinelInfo -and $sentinelInfo.Length -gt 0) {
                Write-Progress -Activity $ActivityLabel -Completed
                throw "Wait aborted for '$ActivityLabel': JIM error watcher reported errors (see $AbortSentinelPath). Activity ID: $ActivityId."
            }
        }

        try {
            $activityProgress = Invoke-JIMApi -Endpoint "/api/v1/activities/$ActivityId/progress"
            $consecutiveAuthFailures = 0
            $status = "$($activityProgress.status ?? 'Running')"

            if ($status -ne $lastStatus) {
                Write-Verbose "${ActivityLabel}: $status"
                $lastStatus = $status
            }

            $elapsed = [int]((Get-Date) - $startTime).TotalSeconds
            $progressParams = Get-JIMActivityProgressDisplay -Progress $activityProgress -ActivityLabel $ActivityLabel -ElapsedSeconds $elapsed
            Write-Progress @progressParams

            if ($status -in $terminalStatuses) {
                Write-Progress -Activity $ActivityLabel -Completed
                return $status
            }
        }
        catch {
            $errorMsg = "$_"

            if ($errorMsg -match 'Authentication failed|session may have expired|API key may be invalid') {
                $consecutiveAuthFailures++

                if ($consecutiveAuthFailures -ge $maxAuthFailures) {
                    Write-Progress -Activity $ActivityLabel -Completed
                    throw "Authentication failed while monitoring activity $ActivityId. The operation was submitted successfully and may still be running on the server. Use Get-JIMActivity -Id $ActivityId to check its status after re-authenticating with Connect-JIM."
                }

                Write-Warning "Authentication error while checking activity status (attempt $consecutiveAuthFailures of $maxAuthFailures). Retrying..."
            }
            else {
                Write-Warning "Error checking activity status: $errorMsg"
            }
        }

        if ($hasTimeout -and ((Get-Date) - $startTime).TotalSeconds -ge $Timeout) {
            Write-Progress -Activity $ActivityLabel -Completed
            return $null
        }

        Start-Sleep -Seconds $PollIntervalSeconds
    }
}