private/RunRecord.ps1

# data-model.md -> Run Record: %LOCALAPPDATA%\Octa\runs\<yyyyMMdd-HHmmss>\run.json
# research.md -> per-user, no elevation needed to write, self-contained per-run folder.

function Get-OctaRunsRoot {
    $rootPath = Join-Path $env:LOCALAPPDATA 'Octa\runs'
    if (-not (Test-Path $rootPath)) {
        New-Item -ItemType Directory -Path $rootPath -Force | Out-Null
    }
    return $rootPath
}

function New-OctaRunFolder {
    <#
        ponytail: the run id is second-resolution, so two runs started within the same second
        used to resolve to the same folder - New-Item -Force silently reuses it and the second
        run's Save-OctaRunRecord overwrites the first one's run.json, losing the snapshots that
        made the first run undoable. Verified real: two back-to-back calls returned the identical
        RunId. A numeric suffix keeps ids sortable (Get-OctaRunRecord's 'latest' relies on
        Sort-Object Name) while guaranteeing each run gets its own folder.
    #>

    [CmdletBinding()]
    param()
    $runsRoot = Get-OctaRunsRoot
    $baseId = Get-Date -Format 'yyyyMMdd-HHmmss'
    $runId = $baseId
    $suffix = 1
    while (Test-Path (Join-Path $runsRoot $runId)) {
        $runId = '{0}-{1:D2}' -f $baseId, $suffix
        $suffix++
    }
    $folder = Join-Path $runsRoot $runId
    New-Item -ItemType Directory -Path $folder -Force | Out-Null
    return [pscustomobject]@{ RunId = $runId; Path = $folder }
}

function Save-OctaRunRecord {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][object]$RunRecord,
        [Parameter(Mandatory)][string]$RunFolder
    )
    $destination = Join-Path $RunFolder 'run.json'
    $RunRecord | ConvertTo-Json -Depth 10 | Set-Content -Path $destination -Encoding utf8
}

function Get-OctaRunRecord {
    <#
        Resolves 'latest' to the most recently created run folder; otherwise looks up the
        given RunId directly. Returns $null when not found (bin/octa.ps1 maps this to exit 4).
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string]$RunId
    )

    $runsRoot = Get-OctaRunsRoot

    if ($RunId -eq 'latest') {
        $latestFolder = Get-ChildItem -Path $runsRoot -Directory -ErrorAction SilentlyContinue |
            Sort-Object Name -Descending | Select-Object -First 1
        if (-not $latestFolder) { return $null }
        $runFile = Join-Path $latestFolder.FullName 'run.json'
    }
    else {
        $runFile = Join-Path $runsRoot "$RunId\run.json"
    }

    if (-not (Test-Path $runFile)) {
        return $null
    }

    $record = Get-Content -Path $runFile -Raw | ConvertFrom-Json
    Add-Member -InputObject $record -MemberType NoteProperty -Name 'RunFolder' -Value (Split-Path $runFile -Parent) -Force
    return $record
}