#!/usr/bin/env pwsh # CASAN L5 Rollback Manager - PowerShell port of rollback-manager.sh # Usage: # rollback-manager.ps1 record # rollback-manager.ps1 execute # rollback-manager.ps1 list # # Exit codes: 0=success, 1=error, 64=usage error param( [Parameter(Mandatory=$true, Position=0)][ValidateSet("record","execute","list")][string]$Mode, [Parameter(Position=1)][string]$Arg1 = "", [Parameter(Position=2)][string]$Arg2 = "" ) $ErrorActionPreference = "Stop" $scriptDir = Split-Path $MyInvocation.MyCommand.Path -Parent $projectRoot = (Resolve-Path (Join-Path $scriptDir "../../..")).Path $logDir = Join-Path $projectRoot ".specify/logs/level5" $txLog = Join-Path $logDir "rollback-transactions.jsonl" if (!(Test-Path $logDir)) { New-Item -ItemType Directory -Force -Path $logDir | Out-Null } function New-TraceId { try { return [System.Guid]::NewGuid().ToString("D") } catch { return "tx-$(Get-Date -Format 'yyyyMMddHHmmss')-$PID" } } switch ($Mode) { "record" { $action = $Arg1 $rollbackCommand = $Arg2 if (!$action -or !$rollbackCommand) { Write-Error "Usage: rollback-manager.ps1 record " exit 64 } $txId = New-TraceId $timestamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ") $gitRef = "" try { $gitRef = (git rev-parse HEAD 2>$null).Trim() } catch {} $line = "{`"timestamp`":`"$timestamp`",`"transaction_id`":`"$txId`",`"action`":`"$action`",`"rollback_command`":`"$(($rollbackCommand -replace '"','\"'))`",`"git_ref`":`"$gitRef`",`"status`":`"recorded`"}" Add-Content -Path $txLog -Value $line -Encoding UTF8 Write-Output "ROLLBACK_RECORDED transaction_id=$txId" exit 0 } "execute" { $txId = $Arg1 if (!$txId) { Write-Error "Usage: rollback-manager.ps1 execute "; exit 64 } if (!(Test-Path $txLog)) { Write-Error "ROLLBACK_NOT_FOUND: transaction log missing"; exit 1 } $found = $false $cmd = "" foreach ($line in (Get-Content $txLog)) { try { $rec = $line | ConvertFrom-Json if ($rec.transaction_id -eq $txId) { $cmd = $rec.rollback_command $found = $true break } } catch {} } if (!$found -or !$cmd) { Write-Error "ROLLBACK_NOT_FOUND transaction_id=$txId" exit 1 } Write-Output "Executing rollback: $cmd" Invoke-Expression $cmd $execExit = $LASTEXITCODE $timestamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ") Add-Content -Path $txLog -Value "{`"timestamp`":`"$timestamp`",`"transaction_id`":`"$txId`",`"status`":`"rolled_back`",`"exit_code`":$execExit}" -Encoding UTF8 Write-Output "ROLLBACK_EXECUTED transaction_id=$txId exit_code=$execExit" exit $execExit } "list" { if (!(Test-Path $txLog)) { Write-Output "No rollback transactions recorded."; exit 0 } $records = Get-Content $txLog | ForEach-Object { try { $_ | ConvertFrom-Json } catch { $null } } | Where-Object { $_ } $records | Format-Table transaction_id, action, status, timestamp -AutoSize exit 0 } }