Payload/scripts/kaden/advisory/AdvisoryReportScan.ps1

function Get-AdvisoryWorkflowRecordFenceBody {
    <#
    .SYNOPSIS
        Returns the advisory-record body. The record ends only when a line is three backticks and nothing else.
    .DESCRIPTION
        Fence text inside the JSON stays in the body. An interior fence sequence does not end the record.
    #>

    param([AllowNull()][string]$Text)
    if ([string]::IsNullOrWhiteSpace([string]$Text)) { return $null }
    $lines = [regex]::Split([string]$Text, "\r?\n")
    $start = -1
    for ($i = 0; $i -lt $lines.Length; $i++) {
        if ($lines[$i].Trim() -eq '```advisory-record') {
            $start = $i
            break
        }
    }
    if ($start -lt 0) { return $null }
    $body = [System.Collections.Generic.List[string]]::new()
    for ($j = $start + 1; $j -lt $lines.Length; $j++) {
        if ($lines[$j].Trim() -eq '```') {
            return ($body -join "`n")
        }
        [void]$body.Add($lines[$j])
    }
    return $null
}

function Read-AdvisoryWorkflowScan {
    param(
        [Parameter(Mandatory = $true)][string]$FeatureRoot,
        [Parameter(Mandatory = $true)][string]$Feature
    )
    $files = @()
    $diagnostics = @()
    $valid = @()
    $seen = @{}
    $unreadable = $false
    foreach ($file in @(Get-AdvisoryWorkflowReportFile -FeatureRoot $FeatureRoot)) {
        $relative = Get-AdvisoryWorkflowRepoRelativeReportPath -Feature $Feature -FileName $file.Name
        try {
            $read = Read-AdvisoryWorkflowReportText -Path $file.FullName
        } catch {
            $unreadable = $true
            $diagnostics += New-AdvisoryWorkflowDiagnostic -Path $relative -Code 'unreadable' -ObservedProblem 'advisory report is unreadable' -Provenance 'report-scan'
            continue
        }
        $files += [pscustomobject]@{ relative_path = $relative; hash = $read.hash; historical = (Test-AdvisoryWorkflowHistoricalText -Text $read.text) }
        if (Test-AdvisoryWorkflowHistoricalText -Text $read.text) { continue }
        # The record ends only on a line that is three backticks. A missing body is partial; an unparseable body is malformed. Both are diagnostics.
        $fenceBody = Get-AdvisoryWorkflowRecordFenceBody -Text $read.text
        if ($null -eq $fenceBody) {
            $diagnostics += New-AdvisoryWorkflowDiagnostic -Path $relative -Code 'partial' -ObservedProblem 'advisory report is partial'
            continue
        }
        $record = Get-AdvisoryWorkflowRecordFromText -Text $read.text
        if ($null -eq $record) {
            $diagnostics += New-AdvisoryWorkflowDiagnostic -Path $relative -Code 'malformed' -ObservedProblem 'advisory report record is malformed'
            continue
        }
        $nameMatch = [regex]::Match($file.Name, $script:AdvisoryWorkflowReportNamePattern)
        $class = Get-AdvisoryReportClassification -Record $record -Feature $Feature -FileTarget $nameMatch.Groups[1].Value `
            -FileIteration ([int]$nameMatch.Groups[2].Value) -SeenKeys $seen
        if ($class.disposition -eq 'exclude') { continue }
        if ($class.disposition -eq 'diagnostic') {
            $diagnostics += New-AdvisoryWorkflowDiagnostic -Path $relative -Code $class.code -ObservedProblem $class.problem
            continue
        }
        $seen[$class.key] = $true
        if ([string](Get-AdvisoryWorkflowProperty -Record $record -Name 'relative_path') -ne $relative) {
            $record | Add-Member -NotePropertyName relative_path -NotePropertyValue $relative -Force
        }
        $valid += $record
    }
    $unreadableDiagnostic = $null
    foreach ($item in @($diagnostics)) {
        if ([string](Get-AdvisoryWorkflowProperty -Record $item -Name 'code') -eq 'unreadable') {
            $unreadableDiagnostic = $item
            break
        }
    }
    return [pscustomobject]@{
        failed      = $unreadable
        diagnostic  = $unreadableDiagnostic
        files       = @($files | Where-Object { -not $_.historical })
        diagnostics = @($diagnostics)
        valid       = @($valid)
    }
}