<#
.SYNOPSIS
  wal-recovery-test.ps1 - rerunnable regression TEST for FIX #3-bis (WAL fold branch rule).

.DESCRIPTION
  Turns the hand-run W0.3 fault-injection (S120, run-folder W0-evidence 4.1/4.2) into a
  test that can be re-run. This is a TEST, not a detector: it EXITS NON-ZERO on failure
  (governance-detectors.ps1 is exit-0-always by mandate; this one is a gate for itself).

  RULE UNDER TEST (FIX #3-bis, spec v3-PATCH):
    K      = git rev-list --count origin/main..HEAD
    BOTTOM = git log --format=%s origin/main..HEAD | LAST line   (= OLDEST commit in range)
      K == 0             -> 'noop'          empty range: no reset, no rebase, commit straight
      BOTTOM starts wal: -> 'reset-soft'    ca1 (NONWAL=0) AND ca2 (NONWAL=1) - SAME handling
      else               -> 'rebase-fixup'  ca3 (bottom = ordinary commit)

    NONWAL IS NOT A BRANCH VARIABLE. Select-WalFoldBranch does not even ACCEPT it as a
    parameter - a structural anchor (asserted at runtime through Get-Command reflection,
    NOT through a grep over source text: a grep would self-match this comment block).

  WHY ca2 KILLS THE OLD RULE (spec v2 :92 branched on NONWAL==0):
    shape base|wal:|[CLAUDE] has NONWAL=1 -> old rule picks REBASE -> todo head = pick wal:
    -> fixup melts the top commit INTO the bottom one and INHERITS THE BOTTOM MESSAGE
    -> range still contains a 'wal:' commit -> push-guard blocks -> re-squash -> same result
    -> permanent STOP. The hazard is that it SUCCEEDS INCORRECTLY: exit 0, no rebase-merge
    residue, no trace. So a test asserting only 'exit code == 0' would PASS on the bug.

  ANTI-TAUTOLOGY ORACLE:
    The expected branch is NOT a restatement of the rule. For every case BOTH candidate
    branches are EXECUTED on independent throwaway trees and the post-state is MEASURED
    (count of 'wal:' commits left in origin/main..HEAD after the fold + re-commit):
      exactly one candidate CLEAN -> expectation is EMPIRICAL (measured, not asserted)
      both candidates CLEAN       -> oracle INCONCLUSIVE -> falls back to the spec-stated
                                     expectation, and the table SAYS SO (honest label)
    ca1/ca2 are EMPIRICAL. ca0/ca3 are DEFINITIONAL - declared, not hidden.

  REGRESSION-GUARD (the real value): at ca2 the test asserts the SUT picks the branch this
  very run MEASURED to be clean, and that the legacy rule picks the branch this very run
  MEASURED to deadlock. Flip Select-WalFoldBranch back to NONWAL-branching and ca2 FAILS.
  Verify the teeth with -InjectLegacyRule (must exit non-zero).

.PARAMETER RepoRoot
  Repo root. Used as a CONTAINMENT ORACLE only: this test must never touch the real .git.
  Containment is proven STRUCTURALLY - every path this script writes derives from -WorkDir,
  and -WorkDir is asserted to sit outside RepoRoot. The RepoRoot HEAD + porcelain snapshot is
  printed as corroboration but deliberately NOT asserted: in a concurrent multi-agent wave a
  sibling lane or the WAL Stop-hook can legitimately move both mid-run, so asserting on them
  would fail for exogenous reasons (a flaky gate is worse than no gate). No repo file is read
  as test material - the trees are synthesized from scratch.

.PARAMETER WorkDir
  Parent dir for the throwaway git trees. Default = $env:TEMP.

.PARAMETER KeepTemp
  Keep the throwaway trees for inspection instead of deleting them.

.PARAMETER InjectLegacyRule
  FAULT-INJECTION SELF-TEST. Swaps the SUT for the legacy NONWAL rule. Expected result:
  ca2 FAILS and the script exits non-zero. Proves this test is not vacuously green.

.EXAMPLE
  powershell.exe -ExecutionPolicy Bypass -File scripts/wal-recovery-test.ps1
  powershell.exe -ExecutionPolicy Bypass -File scripts/wal-recovery-test.ps1 -InjectLegacyRule
#>
param(
    [string]$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path,
    [string]$WorkDir = $env:TEMP,
    [switch]$KeepTemp,
    [switch]$InjectLegacyRule
)

$ErrorActionPreference = 'Continue'

# ---------------------------------------------------------------------------
# Result plumbing
# ---------------------------------------------------------------------------
$script:AssertCount = 0
$script:FailCount = 0
$script:CaseFail = @{}
# PRE-fold measurement per case, captured from real git BEFORE any surgery. The guard
# below must reuse THIS, never re-measure a main tree that has already been folded.
$script:CaseMeasure = @{}
$script:Rows = New-Object System.Collections.Generic.List[object]

function Write-Section($title) {
    Write-Host ''
    Write-Host ("===== $title =====") -ForegroundColor Cyan
}

function Assert-That {
    param([string]$CaseId, [string]$Name, [bool]$Ok, [string]$Detail)
    $script:AssertCount++
    if ($Ok) {
        Write-Host ("  [PASS] {0,-4} | {1,-26} | {2}" -f $CaseId, $Name, $Detail) -ForegroundColor Green
    }
    else {
        Write-Host ("  [FAIL] {0,-4} | {1,-26} | {2}" -f $CaseId, $Name, $Detail) -ForegroundColor Red
        $script:FailCount++
        $script:CaseFail[$CaseId] = $true
    }
}

# ---------------------------------------------------------------------------
# RULE UNDER TEST (SUT) - FIX #3-bis
# Branch variable = BOTTOM of range (+ K for the empty case). NONWAL absent BY DESIGN:
# once the bottom is 'wal:', reset --soft is correct for NONWAL=0 and NONWAL>0 alike,
# so NONWAL cannot change the handling and must not select the branch.
# ---------------------------------------------------------------------------
function Select-WalFoldBranch {
    param([int]$K, [string]$Bottom)
    if ($K -eq 0) { return 'noop' }
    if ($Bottom -match '^wal:') { return 'reset-soft' }
    return 'rebase-fixup'
}

# LEGACY rule (spec v2 :92). Kept ONLY as the regression oracle - never as the SUT
# unless -InjectLegacyRule is passed to prove this test has teeth.
function Select-WalFoldBranchLegacy {
    param([int]$Nonwal)
    if ($Nonwal -eq 0) { return 'reset-soft' }
    return 'rebase-fixup'
}

function Get-FoldBranch {
    param([int]$K, [string]$Bottom, [int]$Nonwal)
    if ($InjectLegacyRule) { return (Select-WalFoldBranchLegacy -Nonwal $Nonwal) }
    return (Select-WalFoldBranch -K $K -Bottom $Bottom)
}

# ---------------------------------------------------------------------------
# git helpers - every path derives from $WorkDir / $RepoRoot, none hardcoded.
# ---------------------------------------------------------------------------
$script:SeqEditor = $null

function New-SeqEditor {
    param([string]$Dir)
    # Sequence editor: keep the FIRST pick, turn every later pick into fixup.
    # Written with LF + no BOM so the git todo parser sees clean lines.
    $p = Join-Path $Dir 'wal-test-seq-editor.ps1'
    $body = @(
        'param($TodoPath)',
        '$lines = Get-Content -LiteralPath $TodoPath',
        '$seen = $false',
        '$out = New-Object System.Collections.Generic.List[string]',
        'foreach ($l in $lines) {',
        '    if ($l -match "^pick ") {',
        '        if ($seen) { $out.Add(($l -replace "^pick ", "fixup ")) }',
        '        else { $seen = $true; $out.Add($l) }',
        '    } else { $out.Add($l) }',
        '}',
        '[System.IO.File]::WriteAllText($TodoPath, (($out -join "`n") + "`n"))',
        'exit 0'
    ) -join "`n"
    [System.IO.File]::WriteAllText($p, $body)
    return $p
}

function New-CaseTree {
    param([string]$Path, [string[]]$Shape)
    if (Test-Path $Path) { Remove-Item -Recurse -Force -LiteralPath $Path }
    New-Item -ItemType Directory -Path $Path -Force | Out-Null
    Push-Location $Path
    git init -q .
    git config user.email 'wal-test@local'
    git config user.name 'wal-test'
    git config commit.gpgsign false
    git config core.autocrlf false
    # Isolate from any inherited hook path: a stray hook must not edit these trees.
    git config core.hooksPath (Join-Path $Path '.git/no-such-hooks')
    [System.IO.File]::WriteAllText((Join-Path $Path 'base.txt'), "base`n")
    git add -- base.txt
    git commit -q -m '[CLAUDE] Infra: base'
    # Fake origin/main as a LOCAL ref - no network, no real remote.
    git update-ref refs/remotes/origin/main HEAD
    $n = 0
    foreach ($subject in $Shape) {
        $n++
        [System.IO.File]::WriteAllText((Join-Path $Path "f$n.txt"), "content $n`n")
        git add -- "f$n.txt"
        git commit -q -m $subject
    }
    Pop-Location
}

function Measure-Range {
    param([string]$Path)
    Push-Location $Path
    $k = [int](git rev-list --count origin/main..HEAD)
    $log = @(git log --format=%s origin/main..HEAD)
    Pop-Location
    $bottom = ''
    if ($log.Count -gt 0) { $bottom = $log[-1] }
    return [pscustomobject]@{
        K      = $k
        Bottom = $bottom
        Nonwal = @($log | Where-Object { -not ($_ -match '^wal:') }).Count
        Nwal   = @($log | Where-Object { $_ -match '^wal:' }).Count
        Log    = $log
    }
}

function Get-RefSnapshot {
    param([string]$Path)
    Push-Location $Path
    $s = @(git for-each-ref --format='%(refname) %(objectname)') -join '|'
    $h = (git rev-parse HEAD)
    Pop-Location
    return ("HEAD=$h REFS=$s")
}

# Executes one fold branch on $Path. Returns what was MEASURED, asserts nothing.
function Invoke-FoldBranch {
    param([string]$Path, [string]$Branch, [int]$K)

    $r = [pscustomobject]@{
        Branch        = $Branch
        RebaseInvoked = $false
        RebaseExit    = $null
        ResetInvoked  = $false
        HeadKEqOrigin = $null
        StateLeft     = $false
        Error         = ''
    }
    Push-Location $Path

    if ($Branch -eq 'noop') {
        # ca0: deliberately do NOTHING. No reset, no rebase.
    }
    elseif ($Branch -eq 'reset-soft') {
        # Safety assert BEFORE touching anything (v2 W0.3 gate).
        $a = (git rev-parse "HEAD~$K")
        $b = (git rev-parse origin/main)
        $r.HeadKEqOrigin = ($a -eq $b)
        if ($r.HeadKEqOrigin) {
            $r.ResetInvoked = $true
            git reset --soft "HEAD~$K" | Out-Null
            git diff --cached --quiet
            if ($LASTEXITCODE -ne 0) { git commit -q -m '[CLAUDE] Infra: fold re-commit' }
        }
        else {
            $r.Error = 'ABORT: HEAD~K != origin/main'
        }
    }
    elseif ($Branch -eq 'rebase-fixup') {
        $env:GIT_SEQUENCE_EDITOR = ('powershell.exe -NoProfile -ExecutionPolicy Bypass -File "' + $script:SeqEditor + '"')
        # Hang-guard: never let git open an interactive message editor.
        $env:GIT_EDITOR = 'powershell.exe -NoProfile -Command exit 0'
        $r.RebaseInvoked = $true
        git rebase -i origin/main 2>$null | Out-Null
        $r.RebaseExit = $LASTEXITCODE
        if ($r.RebaseExit -ne 0) { git rebase --abort 2>$null | Out-Null }
        $env:GIT_SEQUENCE_EDITOR = $null
        $env:GIT_EDITOR = $null
    }

    # Rebase-state residue check after EVERY step, on every branch.
    $r.StateLeft = ((Test-Path '.git/rebase-merge') -or (Test-Path '.git/rebase-apply'))
    Pop-Location
    return $r
}

# Runs a candidate branch on a FRESH tree and reports the measured outcome.
function Test-Candidate {
    param([string]$Root, [string]$Id, [string[]]$Shape, [string]$Branch)
    $p = Join-Path $Root $Id
    New-CaseTree -Path $p -Shape $Shape
    $pre = Measure-Range -Path $p
    $exec = Invoke-FoldBranch -Path $p -Branch $Branch -K $pre.K
    $post = Measure-Range -Path $p
    $outcome = 'CLEAN'
    if ($post.Nwal -gt 0) { $outcome = 'DEADLOCK' }
    return [pscustomobject]@{
        Branch = $Branch; Exec = $exec; Post = $post; Outcome = $outcome; Path = $p
    }
}

# ---------------------------------------------------------------------------
# Bootstrap + containment
# ---------------------------------------------------------------------------
Write-Section 'setup + containment (RepoRoot must stay untouched)'

$stamp = (Get-Date -Format 'yyyyMMddTHHmmss')
$root = Join-Path $WorkDir ("wal-recovery-test-" + $stamp)
New-Item -ItemType Directory -Path $root -Force | Out-Null
$script:SeqEditor = New-SeqEditor -Dir $root

Write-Host ("  RepoRoot = {0}" -f $RepoRoot)
Write-Host ("  WorkDir  = {0}" -f $root)
if ($InjectLegacyRule) {
    Write-Host '  MODE     = FAULT-INJECT (-InjectLegacyRule): SUT swapped to legacy NONWAL rule.' -ForegroundColor Magenta
    Write-Host '             EXPECTED RESULT = ca2 FAIL + non-zero exit.' -ForegroundColor Magenta
}

# Containment (a) HARD ASSERT: every tree this script builds hangs off $root, so proving
# $root is outside RepoRoot proves the real .git is unreachable. Structural, not flaky.
$rrFull = ($RepoRoot -replace '/', '\').TrimEnd('\')
$wdFull = ($root -replace '/', '\')
Assert-That 'env' 'workdir-outside-repo' (-not $wdFull.StartsWith(($rrFull + '\'), [StringComparison]::OrdinalIgnoreCase)) `
    ("WorkDir not under RepoRoot -> real .git cannot be reached")

# Containment (b) INFO ONLY: snapshot the real repo, re-printed at the end. NOT asserted -
# see .PARAMETER RepoRoot: concurrent lanes / the WAL Stop-hook move these legitimately.
$repoIsGit = Test-Path (Join-Path $RepoRoot '.git')
$repoHeadBefore = 'n/a'
$repoDirtyBefore = -1
if ($repoIsGit) {
    $repoHeadBefore = (git -C $RepoRoot rev-parse HEAD)
    $repoDirtyBefore = @(git -C $RepoRoot status --porcelain).Count
    Write-Host ("  RepoRoot HEAD before = {0} ; porcelain entries = {1}" -f $repoHeadBefore, $repoDirtyBefore)
}
else {
    Write-Host '  [note] RepoRoot is not a git repo - containment (b) reduced to path check only' -ForegroundColor DarkGray
}

# Structural anchor: the SUT must not be ABLE to branch on NONWAL.
$sutParams = @((Get-Command Select-WalFoldBranch).Parameters.Keys)
$hasNonwal = $false
foreach ($pn in $sutParams) { if ($pn -match '(?i)nonwal') { $hasNonwal = $true } }
Assert-That 'env' 'sut-has-no-NONWAL-param' (-not $hasNonwal) `
    ("Select-WalFoldBranch params = [{0}] (structural, via Get-Command - not a source grep)" -f ($sutParams -join ', '))

# ---------------------------------------------------------------------------
# The 4 cases. Shape = commits laid on top of base, BOTTOM-FIRST.
# ---------------------------------------------------------------------------
$WAL1 = 'wal: flush 1'
$WAL2 = 'wal: flush 2'
$REAL = '[CLAUDE] Infra: real work'

$cases = @(
    [pscustomobject]@{ Id = 'ca0'; Shape = @();                Label = 'base';                SpecBranch = 'noop' },
    [pscustomobject]@{ Id = 'ca1'; Shape = @($WAL1, $WAL2);    Label = 'base|wal:|wal:';      SpecBranch = 'reset-soft' },
    [pscustomobject]@{ Id = 'ca2'; Shape = @($WAL1, $REAL);    Label = 'base|wal:|[CLAUDE]';  SpecBranch = 'reset-soft' },
    [pscustomobject]@{ Id = 'ca3'; Shape = @($REAL, $WAL1);    Label = 'base|[CLAUDE]|wal:';  SpecBranch = 'rebase-fixup' }
)

foreach ($c in $cases) {
    $script:CaseFail[$c.Id] = $false
    Write-Section ("{0} : {1}" -f $c.Id, $c.Label)

    # --- 1. build the tree and MEASURE the branch inputs from real git ---
    $main = Join-Path $root ($c.Id + '-main')
    New-CaseTree -Path $main -Shape $c.Shape
    $m = Measure-Range -Path $main
    $script:CaseMeasure[$c.Id] = $m
    Write-Host ("  measured: K={0} NONWAL={1} BOTTOM=[{2}]" -f $m.K, $m.Nonwal, $m.Bottom)
    foreach ($l in $m.Log) { Write-Host ("    range: {0}" -f $l) -ForegroundColor DarkGray }

    $sut = Get-FoldBranch -K $m.K -Bottom $m.Bottom -Nonwal $m.Nonwal
    $legacy = Select-WalFoldBranchLegacy -Nonwal $m.Nonwal
    Write-Host ("  SUT picks   = {0}" -f $sut)
    Write-Host ("  legacy rule = {0}   (branches on NONWAL - spec v2 :92)" -f $legacy)

    # --- 2. ORACLE: execute BOTH candidates on independent trees, measure outcome ---
    $cfReset = Test-Candidate -Root $root -Id ($c.Id + '-cf-reset') -Shape $c.Shape -Branch 'reset-soft'
    $cfRebase = Test-Candidate -Root $root -Id ($c.Id + '-cf-rebase') -Shape $c.Shape -Branch 'rebase-fixup'
    Write-Host ("  counterfactual reset-soft   -> n(wal: in range)={0} -> {1}" -f $cfReset.Post.Nwal, $cfReset.Outcome)
    Write-Host ("  counterfactual rebase-fixup -> n(wal: in range)={0} -> {1}  [rebase-exit={2}]" -f `
            $cfRebase.Post.Nwal, $cfRebase.Outcome, $cfRebase.Exec.RebaseExit)

    $clean = @()
    if ($cfReset.Outcome -eq 'CLEAN') { $clean += 'reset-soft' }
    if ($cfRebase.Outcome -eq 'CLEAN') { $clean += 'rebase-fixup' }

    $oracleKind = 'DEFINITIONAL'
    $expected = $c.SpecBranch
    if ($c.Id -ne 'ca0' -and $clean.Count -eq 1) {
        $oracleKind = 'EMPIRICAL'
        $expected = $clean[0]
    }
    if ($oracleKind -eq 'EMPIRICAL') {
        Write-Host ("  ORACLE = EMPIRICAL: exactly one candidate survives the push-guard -> expected={0}" -f $expected) -ForegroundColor Yellow
    }
    else {
        Write-Host ("  ORACLE = DEFINITIONAL: both candidates outcome-equivalent on the n(wal:) metric") -ForegroundColor DarkGray
        Write-Host ("           -> expected={0} taken from spec v3-PATCH, NOT measured. Declared, not hidden." -f $expected) -ForegroundColor DarkGray
    }

    # --- 3. execute the SUT-chosen branch on the main tree ---
    $snapBefore = Get-RefSnapshot -Path $main
    $exec = Invoke-FoldBranch -Path $main -Branch $sut -K $m.K
    $snapAfter = Get-RefSnapshot -Path $main
    $post = Measure-Range -Path $main

    # --- 4. asserts ---
    Assert-That $c.Id 'branch-selection' ($sut -eq $expected) `
        ("SUT={0} expected={1} ({2} oracle)" -f $sut, $expected, $oracleKind)

    Assert-That $c.Id 'no-rebase-state-left' (-not $exec.StateLeft) `
        ('.git/rebase-merge + .git/rebase-apply ABSENT after the step')
    Assert-That $c.Id 'cf-no-rebase-state' ((-not $cfReset.Exec.StateLeft) -and (-not $cfRebase.Exec.StateLeft)) `
        ('.git/rebase-merge ABSENT on BOTH counterfactual trees too')

    # REBASE-EXIT is never non-zero - on the main tree AND on the counterfactual that rebased.
    $rebaseExits = @()
    if ($null -ne $exec.RebaseExit) { $rebaseExits += $exec.RebaseExit }
    if ($null -ne $cfRebase.Exec.RebaseExit) { $rebaseExits += $cfRebase.Exec.RebaseExit }
    $badExit = @($rebaseExits | Where-Object { $_ -ne 0 }).Count
    Assert-That $c.Id 'rebase-exit-never-nonzero' ($badExit -eq 0) `
        ("rebase exits observed = [{0}] ; non-zero count = {1}" -f (($rebaseExits | ForEach-Object { "$_" }) -join ','), $badExit)

    if ($sut -eq 'reset-soft') {
        Assert-That $c.Id 'headK-eq-origin-pre-reset' ($exec.HeadKEqOrigin -eq $true) `
            ("HEAD~K == origin/main asserted BEFORE reset (K={0})" -f $m.K)
    }

    if ($c.Id -eq 'ca0') {
        Assert-That $c.Id 'noop-touched-nothing' ((-not $exec.ResetInvoked) -and (-not $exec.RebaseInvoked)) `
            ('no reset, no rebase issued on an empty range')
        Assert-That $c.Id 'git-tree-identical' ($snapBefore -eq $snapAfter) `
            ('.git ref-state byte-identical before/after')
    }

    if ($c.Id -eq 'ca2') {
        # v3-PATCH acceptance: ca2 must not touch git rebase AT ALL.
        Assert-That $c.Id 'ca2-never-rebases' (-not $exec.RebaseInvoked) `
            ('git rebase NOT invoked on ca2')
    }

    if ($oracleKind -eq 'EMPIRICAL') {
        Assert-That $c.Id 'post-state-clean' ($post.Nwal -eq 0) `
            ("n(wal: in origin/main..HEAD) after SUT branch = {0}" -f $post.Nwal)
    }

    $verdict = 'PASS'
    if ($script:CaseFail[$c.Id]) { $verdict = 'FAIL' }
    $script:Rows.Add([pscustomobject]@{
            Case = $c.Id; Shape = $c.Label; K = $m.K; Nonwal = $m.Nonwal
            Sut = $sut; Legacy = $legacy; Expected = $expected; Oracle = $oracleKind; Verdict = $verdict
        })
}

# ---------------------------------------------------------------------------
# REGRESSION-GUARD - the actual value of this script.
# Not a restatement of the rule: both claims below are MEASURED this run.
# ---------------------------------------------------------------------------
Write-Section 'REGRESSION-GUARD (ca2) - legacy NONWAL rule must stay rejected'

$c2 = $cases | Where-Object { $_.Id -eq 'ca2' }
$g1 = Test-Candidate -Root $root -Id 'guard-ca2-legacy' -Shape $c2.Shape -Branch 'rebase-fixup'
# PRE-fold inputs of ca2, captured from real git in the loop above. Re-measuring
# ca2-main here would read the ALREADY-FOLDED tree (K=1, bottom = the re-commit) and
# silently ask the rule a different question than the one under test.
$m2 = $script:CaseMeasure['ca2']

Write-Host '  Replay of W0.3 4.2 - legacy branch (rebase+fixup) executed on a ca2 tree:'
Write-Host ("    rebase-exit           = {0}   (0 = it SUCCEEDS, which is exactly the hazard)" -f $g1.Exec.RebaseExit)
Write-Host ("    rebase-state left     = {0}" -f $g1.Exec.StateLeft)
foreach ($l in $g1.Post.Log) { Write-Host ("    after: {0}" -f $l) -ForegroundColor DarkGray }
Write-Host ("    n(wal: in range)      = {0}" -f $g1.Post.Nwal)

# Fixed-point probe: re-folding the deadlocked range never escapes it.
$g2 = $null
if ($g1.Post.Nwal -gt 0) {
    $g2 = Invoke-FoldBranch -Path $g1.Path -Branch 'rebase-fixup' -K $g1.Post.K
    $after2 = Measure-Range -Path $g1.Path
    Write-Host ("    re-squash (2nd fold)  -> n(wal:)={0}  [rebase-exit={1}] -> fixed point = PERMANENT STOP" -f `
            $after2.Nwal, $g2.RebaseExit)
    Assert-That 'grd' 'deadlock-is-fixed-point' ($after2.Nwal -gt 0) `
        ('re-squashing the legacy result still leaves a wal: commit -> push-guard blocks forever')
}

Assert-That 'grd' 'legacy-branch-deadlocks' (($g1.Exec.RebaseExit -eq 0) -and ($g1.Post.Nwal -gt 0)) `
    ("MEASURED: legacy branch exits 0 yet leaves n(wal:)={0} -> succeeds INCORRECTLY" -f $g1.Post.Nwal)

$legacyCa2 = Select-WalFoldBranchLegacy -Nonwal $m2.Nonwal
$sutCa2 = Get-FoldBranch -K $m2.K -Bottom $m2.Bottom -Nonwal $m2.Nonwal

Assert-That 'grd' 'legacy-picks-the-bad-one' ($legacyCa2 -eq 'rebase-fixup') `
    ("legacy(NONWAL={0}) = {1} = the branch measured to deadlock" -f $m2.Nonwal, $legacyCa2)
Assert-That 'grd' 'sut-rejects-legacy-branch' ($sutCa2 -ne 'rebase-fixup') `
    ("SUT = {0} != the measured-deadlock branch. Revert the rule to NONWAL and THIS assert fails." -f $sutCa2)
Assert-That 'grd' 'rules-diverge-at-ca2' ($sutCa2 -ne $legacyCa2) `
    ("SUT={0} vs legacy={1} - divergence is what makes this guard non-vacuous" -f $sutCa2, $legacyCa2)

# ---------------------------------------------------------------------------
# Result table
# ---------------------------------------------------------------------------
Write-Section 'RESULT'

Write-Host ('{0,-5} {1,-22} {2,-3} {3,-7} {4,-13} {5,-13} {6,-13} {7,-13} {8}' -f `
        'CASE', 'SHAPE', 'K', 'NONWAL', 'SUT', 'LEGACY', 'EXPECTED', 'ORACLE', 'VERDICT')
Write-Host ('-' * 118)
foreach ($r in $script:Rows) {
    $col = 'Green'
    if ($r.Verdict -eq 'FAIL') { $col = 'Red' }
    Write-Host ('{0,-5} {1,-22} {2,-3} {3,-7} {4,-13} {5,-13} {6,-13} {7,-13} {8}' -f `
            $r.Case, $r.Shape, $r.K, $r.Nonwal, $r.Sut, $r.Legacy, $r.Expected, $r.Oracle, $r.Verdict) -ForegroundColor $col
}

$passCases = @($script:Rows | Where-Object { $_.Verdict -eq 'PASS' }).Count
$totalCases = $script:Rows.Count

# Containment (b) re-check - INFO, never a verdict input (concurrency-exogenous).
Write-Host ''
if ($repoIsGit) {
    $repoHeadAfter = (git -C $RepoRoot rev-parse HEAD)
    $repoDirtyAfter = @(git -C $RepoRoot status --porcelain).Count
    $same = (($repoHeadAfter -eq $repoHeadBefore) -and ($repoDirtyAfter -eq $repoDirtyBefore))
    Write-Host ("  [info] RepoRoot HEAD {0} -> {1} ; porcelain {2} -> {3} ; unchanged={4}" -f `
            $repoHeadBefore.Substring(0, 7), $repoHeadAfter.Substring(0, 7), $repoDirtyBefore, $repoDirtyAfter, $same)
    if (-not $same) {
        Write-Host '  [info] delta above is NOT a failure: containment is proven by workdir-outside-repo.' -ForegroundColor DarkGray
        Write-Host '         A concurrent lane or the WAL Stop-hook can move HEAD/porcelain mid-run.' -ForegroundColor DarkGray
    }
}

# ---------------------------------------------------------------------------
# Cleanup + exit. pass/fail COMPUTED from the counters - never a pasted label.
# ---------------------------------------------------------------------------
if ($KeepTemp) {
    Write-Host ("temp trees KEPT at: {0}" -f $root) -ForegroundColor Yellow
}
else {
    Set-Location $WorkDir
    Remove-Item -Recurse -Force -LiteralPath $root -ErrorAction SilentlyContinue
    Write-Host ("temp trees removed ({0}) - pass -KeepTemp to inspect" -f $root) -ForegroundColor DarkGray
}

Write-Host ''
Write-Host ("cases {0}/{1} PASS | asserts {2} run, {3} failed" -f $passCases, $totalCases, $script:AssertCount, $script:FailCount) -ForegroundColor Cyan

$exitCode = 0
if ($script:FailCount -gt 0) { $exitCode = 1 }
if ($passCases -ne $totalCases) { $exitCode = 1 }

if ($exitCode -eq 0) {
    Write-Host 'VERDICT: PASS' -ForegroundColor Green
}
else {
    Write-Host 'VERDICT: FAIL' -ForegroundColor Red
    if ($InjectLegacyRule) {
        Write-Host 'NOTE: -InjectLegacyRule was set. A FAIL here is the EXPECTED result: it proves the test has teeth.' -ForegroundColor Magenta
    }
}
exit $exitCode
