src/Record.ps1

function Save-QuietEvent {
    param([string]$Id,[string]$Kind,[string]$Command,$Payload)
    $json=ConvertTo-Json -InputObject $Payload -Depth 64 -Compress
    Invoke-QuietSql 'INSERT OR REPLACE INTO quietcli_v2_events(id,kind,timestamp_ms,command,payload) VALUES(?,?,?,?,?)' @($Id,$Kind,[DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds(),$Command,$json)
}
function Compress-QuietText {
    param([AllowNull()][string]$Text)
    $buffer=[IO.MemoryStream]::new();$zip=[IO.Compression.GZipStream]::new($buffer,[IO.Compression.CompressionLevel]::Optimal,$true)
    try{$bytes=[Text.Encoding]::UTF8.GetBytes($Text);$zip.Write($bytes,0,$bytes.Length)}finally{$zip.Dispose()}
    try{[Convert]::ToBase64String($buffer.ToArray())}finally{$buffer.Dispose()}
}
function Expand-QuietText {
    param([string]$Text)
    if(!$Text){return ''}
    $memory=[IO.MemoryStream]::new([Convert]::FromBase64String($Text))
    $zip=[IO.Compression.GZipStream]::new($memory,[IO.Compression.CompressionMode]::Decompress)
    $reader=[IO.StreamReader]::new($zip,[Text.UTF8Encoding]::new($false))
    try{$reader.ReadToEnd()}finally{$reader.Dispose()}
}
function Save-QuietRecord {
    param($Result,[switch]$Explicit)
    if(!$Result.Internal.Sources.Count -and !$Result.Internal.Records.Count){return}
    try{
        $settings=Get-QuietConfiguration;$protection=@{Limited=$false;Redacted=$false}
        $sources=@(foreach($source in $Result.Internal.Sources.Values){
                        $capture=$source.Capture
            $request=$source.Request.Clone();$argv=@($request.ArgumentList)
            for($i=0;$i -lt $argv.Count;$i++){
                if($argv[$i] -match '^--?(?:password|passwd|secret|token|api[-_]?key)$' -and $i+1 -lt $argv.Count){$argv[++$i]='[REDACTED]';$protection.Redacted=$true}
            }
            $request.ArgumentList=$argv
            $copies=@(foreach($path in @($source.SourcePaths|Select-Object -Unique)){
                if(!$path -or !(Test-Path -LiteralPath $path -PathType Leaf)){continue}
                $content=Get-Content -LiteralPath $path -Raw
                $maskState=@{Limited=$false;Redacted=$false}
                $safeContent=Protect-QuietValue $content $maskState
                @{Name=[IO.Path]::GetFileName($path);SHA256=(Get-FileHash -LiteralPath $path -Algorithm SHA256).Hash
                    Text=$safeContent;Bytes=$(if(!$maskState.Redacted -and !$maskState.Limited){[Convert]::ToBase64String([IO.File]::ReadAllBytes($path))}else{$null});ModifiedByMasking=($maskState.Redacted -or $maskState.Limited);Path=(Resolve-Path -LiteralPath $path).Path}
            })
            $streams=@{}
            foreach($name in @('stdout','stderr')){$text=if($capture){$capture.ReadText($name)}else{''};$streams[$name]=Compress-QuietText (Protect-QuietValue $text $protection)}
            @{Id=$source.Id;Command=$source.Command;Request=(Protect-QuietValue $request $protection)
                                OriginalRequest=$(if($source.ContainsKey('OriginalRequest')){
                    $original=$source.OriginalRequest.Clone();$words=@($original.ArgumentList)
                    for($j=0;$j -lt $words.Count;$j++){if($words[$j] -match '^--?(?:password|passwd|secret|token|api[-_]?key)$' -and $j+1 -lt $words.Count){$words[++$j]='[REDACTED]';$protection.Redacted=$true}}
                    $original.ArgumentList=$words;Protect-QuietValue $original $protection
                }else{$null})
                Contract=$source.Contract;Documentation=@($source.Documentation);ObservedVersion=$source.ObservedVersion
                Sources=$copies;Promotable=($copies.Count -gt 0 -and $copies.Count -eq @($source.SourcePaths|Select-Object -Unique).Count -and @($copies|Where-Object ModifiedByMasking).Count -eq 0)
                Encoding=$source.Encoding;RecordDelimiter=$source.RecordDelimiter;StdErrDelimiter=$source.StdErrDelimiter
                SourceComplete=$source.SourceComplete;AnalysisComplete=$source.AnalysisComplete
                CaptureComplete=[bool]($capture -and $capture.CaptureComplete);Lossy=[bool]($capture -and $capture.Lossy)
                                Outcome=$source.Outcome;ExitCode=$source.ExitCode;Bytes=$source.Bytes;Streams=$streams
                RecordOrderComplete=[bool]($capture -and $capture.JournalComplete)
                                Frames=$(if($capture -and $capture.JournalComplete){
                    $frames=@($capture.ReadRecords()|ForEach-Object {[pscustomobject]@{Stream=$_.Stream;Text=$_.Text;Sequence=$_.Sequence;Truncated=$_.Truncated}})
                    $safeFrames=Protect-QuietValue $frames $protection
                    Compress-QuietText (ConvertTo-Json -InputObject $safeFrames -Depth 10 -Compress)
                }else{$null})}
        })
        $id=$Result.Internal.Id.Substring(0,12)
        $payload=@{SchemaVersion=2;Id=$id;TimestampUtc=[DateTimeOffset]::UtcNow.ToString('o');Explicit=[bool]$Explicit
            Outcome=$Result.Outcome;Completeness=$Result.Completeness;Sources=$sources;Children=@($Result.Internal.Records)
            Promotable=($sources.Count -gt 0 -and @($sources|Where-Object {!$_.Promotable}).Count -eq 0)
            Data=(Protect-QuietValue $Result.Data $protection);Masked=$protection.Redacted}
        if($protection.Limited){throw 'Masking could not preserve a complete record.'}
        $command=if($sources.Count -eq 1){$sources[0].Command}else{'task'}
        Save-QuietEvent $id record $command $payload
        $cutoff=[DateTimeOffset]::UtcNow.AddDays(-$settings.RecallRetentionDays).ToUnixTimeMilliseconds()
        Invoke-QuietSql "DELETE FROM quietcli_v2_events WHERE kind='record' AND (timestamp_ms < ? OR id NOT IN (SELECT id FROM quietcli_v2_events WHERE kind='record' ORDER BY timestamp_ms DESC,id DESC LIMIT ?))" @($cutoff,$settings.RecallMaxEntries)
        $Result.RecordId=$id
        return $id
    }catch{$script:LastGainError=$_.Exception.GetBaseException().Message;$Result.Measurement.Status='RecordUnavailable'}
}
function Read-QuietRecord {
    param([Parameter(Mandatory)][string]$Id)
    $rows=@(Invoke-QuietSql -Read -Sql "SELECT payload FROM quietcli_v2_events WHERE id=? AND kind='record'" -Values @($Id))
    if(!$rows.Count){throw "Unknown or expired QuietCli record: $Id"}
    $rows[0]['payload']|ConvertFrom-Json -AsHashtable -Depth 64
}
function Get-QuietRecord {
    [CmdletBinding()]param([string]$Id,[switch]$List,[switch]$AsObject,[ValidateSet('stdout','stderr')][string]$Stream,
        [int]$SourceIndex=0,[int]$From=1,[int]$Lines=20,[string]$Grep,[int]$MaxItems=20,[int]$MaxOutputBytes=4096)
    try{
        if($List -or !$Id){
            $rows=@(Invoke-QuietSql -Read -Sql "SELECT id,timestamp_ms,command,payload FROM quietcli_v2_events WHERE kind='record' ORDER BY timestamp_ms DESC,id DESC LIMIT ?" -Values @($MaxItems))
            $data=@($rows|ForEach-Object {$p=$_.payload|ConvertFrom-Json;[pscustomobject]@{Id=$_.id;Command=$_.command;TimestampUtc=$p.TimestampUtc;Outcome=$p.Outcome;Promotable=$p.Promotable;Explicit=$p.Explicit}})
        }else{
            $record=Read-QuietRecord $Id
            if($AsObject){return $record}
            if($Stream){
                if($From -lt 1 -or $Lines -lt 0 -or $SourceIndex -lt 0 -or $SourceIndex -ge $record.Sources.Count){throw 'Invalid record window.'}
                $text=Expand-QuietText $record.Sources[$SourceIndex].Streams[$Stream]
                $all=@($text -split '\r?\n');$selected=[Collections.Generic.List[object]]::new()
                for($i=$From-1;$i -lt $all.Count -and $selected.Count -lt $Lines;$i++){if(!$Grep -or $all[$i] -match $Grep){$selected.Add(@{Line=$i+1;Text=$all[$i]})}}
                $data=@{Id=$Id;Stream=$Stream;TotalLines=$all.Count;Lines=$selected.ToArray();CaptureComplete=$record.Sources[$SourceIndex].CaptureComplete}
            }else{$data=@{Id=$Id;Outcome=$record.Outcome;Completeness=$record.Completeness;Promotable=$record.Promotable;Sources=@($record.Sources|ForEach-Object {@{Command=$_.Command;Contract=$_.Contract;Request=$_.Request;Documentation=$_.Documentation;CaptureComplete=$_.CaptureComplete}})}}
            if((Get-QuietConfiguration).Tracking){Save-QuietEvent ([guid]::NewGuid().ToString('N')) recall 'record' @{RecordId=$Id}}
        }
        $result=New-QuietResult -Data $data -NoRegister
    }catch{$result=New-QuietResult -Outcome Failed -ErrorInfo @{Code='RecordReadFailed';Message=$_.Exception.Message} -NoRegister}
    if($AsObject){$result}else{Out-QuietResult $result -MaxItems $MaxItems -MaxOutputBytes $MaxOutputBytes}
}
function Invoke-QuietCapturedCall {
    param([Parameter(Mandatory)]$Source,[Parameter(Mandatory)]$Contract)
    if(!$Source.CaptureComplete -or $Source.Lossy){throw 'Cannot certify replay from an incomplete or lossy capture.'}
    $capture=[pscustomobject]@{Stdout=(Expand-QuietText $Source.Streams.stdout);Stderr=(Expand-QuietText $Source.Streams.stderr)
        SourceComplete=$Source.SourceComplete;CaptureComplete=$Source.CaptureComplete;ExitCode=$Source.ExitCode;LostRecords=0
        TimedOut=($Source.Outcome -eq 'Timeout');Cancelled=($Source.Outcome -eq 'Cancelled')}
    if($Contract.Parse){$values=@(Invoke-QuietCallback $Contract.Parse @($capture))}
    else{
        if(!$Source.RecordOrderComplete){throw 'Streaming replay requires a complete observed record order.'}
        $frames=Expand-QuietText $Source.Frames|ConvertFrom-Json
        $emissions=[Collections.Generic.List[object]]::new()
        foreach($frame in $frames){
            $items=if($Contract.OnBatch){@(Invoke-QuietCallback $Contract.OnBatch @((,@($frame)),$Contract.State))}else{@(Invoke-QuietCallback $Contract.OnOutput @($frame,$Contract.State))}
            foreach($item in $items){$emissions.Add($item)}
        }
        # Keep a single callback-produced dictionary as an item before indexing the result.
        if($Contract.Complete){$values=@(Invoke-QuietCallback $Contract.Complete @($Contract.State,$capture))}
        else{$values=@($emissions.ToArray())}
    }
    $data=if($values.Count -eq 1){$values[0]}elseif($values.Count){,$values}else{$null}
    $outcome=if($Source.ExitCode -in $Contract.SuccessExitCodes){'Success'}else{$Source.Outcome}
    $errorInfo=if($outcome -ne 'Success'){@{Code='NativeFailed';Phase='Replay'}}else{$null}
    $result=New-QuietResult -Data $data -Outcome $outcome -Projection $Contract.Projection -Execution @{ExitCode=$Source.ExitCode;Replay=$true} -ErrorInfo $errorInfo
    $result.Completeness.Source=$Source.SourceComplete;$result.Completeness.Analysis=$Source.SourceComplete
    $result
}
function Invoke-QuietRecord {
    [CmdletBinding()]param([Parameter(Mandatory)][string]$Id,[string]$ContractPath,[object]$Contract,[switch]$AsObject,[int]$MaxOutputBytes=4096)
    $previous=$script:QuietReplay;$previousContext=$script:QuietReplayContext;$script:QuietReplay=$true;$result=$null
    try{
        $record=Read-QuietRecord $Id;$results=[Collections.Generic.List[object]]::new()
        foreach($source in $record.Sources){
            $invocation=if($source.OriginalRequest){$source.OriginalRequest}else{$source.Request};$candidate=$Contract
            if($ContractPath){$candidate=Invoke-QuietFactory -Path $ContractPath -Invocation $invocation}
            elseif(!$candidate){$route=Resolve-QuietCommand $source.Command;if($route){$candidate=Invoke-QuietFactory -Factory $route.Factory -Invocation $invocation}}
            if(!$candidate -or $candidate.PSObject.TypeNames -notcontains 'QuietCli.Contract'){throw 'Replay requires an explicit applicable contract or an installed module.'}
            if($candidate.Execute){
                $queue=[Collections.Generic.Queue[object]]::new();foreach($step in $record.Sources){$queue.Enqueue($step)}
                $script:QuietReplayContext=$queue;$result=Invoke-QuietContract $candidate $invocation @{} $ContractPath
                if($queue.Count){throw 'The replay contract did not consume every recorded native call.'}
                break
            }
            $results.Add((Invoke-QuietCapturedCall $source $candidate))
        }
        if(!$result){
            if($results.Count -eq 1){$result=$results[0]}else{
                $failed=@($results|Where-Object Outcome -ne 'Success');$outcome=if($failed.Count){$failed[0].Outcome}else{'Success'}
                $result=New-QuietResult -Outcome $outcome -Data @($results|ForEach-Object Data) -NoRegister -Execution @{Steps=$results.Count;Replay=$true}
            }
        }
    }catch{$result=New-QuietResult -Outcome Failed -ErrorInfo @{Code='ReplayFailed';Message=$_.Exception.Message} -NoRegister}
    finally{$script:QuietReplay=$previous;$script:QuietReplayContext=$previousContext}
    if($AsObject){$result}else{Out-QuietResult $result -MaxOutputBytes $MaxOutputBytes}
}