Public/Get-HDTRunLogRecord.ps1

function Get-HDTRunLogRecord {
    <#
        .SYNOPSIS
            Reads a run's JSONL back into records.
 
        .DESCRIPTION
            ONE READER, TWO SCREENS. The progress window and the failure window
            are both derived from the JSONL the engine writes anyway (DESIGN
            11.1: one source of truth, so the screen and the log cannot
            disagree) - and until this existed, reading it was fifteen lines
            copied into whichever command needed them. Two copies of "read the
            log" is two answers about a half-written line.
 
            A HALF-WRITTEN LINE IS SKIPPED, NEVER FATAL. A JSONL whose last line
            is a fragment is exactly what a machine that died mid-step leaves
            behind, and that is precisely the run somebody wants to read. JSON
            Lines is one object per physical line so that a reader can do this.
 
            IT NEVER THROWS FOR A MISSING LOG. A run that failed before the log
            context existed has nothing to read, and a reader that threw would
            replace the real failure with its own.
 
        .PARAMETER Context
            The log context from New-HDTLogContext. Its JsonlPath and FileSystem
            are the only things read.
 
        .INPUTS
            None. This command does not accept pipeline input.
 
        .OUTPUTS
            System.Management.Automation.PSCustomObject[] - the records, oldest
            first. An empty array when there is nothing to read.
 
        .EXAMPLE
            $record = Get-HDTRunLogRecord -Context $log
            Get-HDTDeploymentProgress -Record $record
    #>

    [CmdletBinding()]
    [OutputType([pscustomobject[]])]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [AllowNull()]
        [object] $Context
    )

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

    $record = New-Object -TypeName System.Collections.ArrayList

    try {
        if ($null -eq $Context) { return [pscustomobject[]] @() }
        if ($null -eq $Context.PSObject.Properties['JsonlPath'] -or
            $null -eq $Context.PSObject.Properties['FileSystem']) {
            return [pscustomobject[]] @()
        }

        $path = [string] $Context.JsonlPath
        if ([string]::IsNullOrWhiteSpace($path)) { return [pscustomobject[]] @() }

        $fileSystem = $Context.FileSystem
        if ($null -eq $fileSystem -or -not $fileSystem.TestPath($path)) { return [pscustomobject[]] @() }

        $text = [string] $fileSystem.ReadAllText($path)
        if ([string]::IsNullOrWhiteSpace($text)) { return [pscustomobject[]] @() }

        foreach ($line in ($text -split "`n")) {

            $trimmed = $line.Trim()
            if ([string]::IsNullOrWhiteSpace($trimmed)) { continue }

            # PER LINE, BECAUSE ONE BAD LINE MUST NOT COST THE OTHERS.
            try {
                [void] $record.Add((ConvertFrom-Json -InputObject $trimmed))
            } catch {
                continue
            }
        }
    } catch {
        # See the header: a reader that threw would replace the failure it was
        # called to explain.
        Write-Verbose ("the run log could not be read: {0}" -f [string] $_.Exception.Message)
    }

    return [pscustomobject[]] @($record)
}