From 04d146c0469028a89b27b939439b9b7cf1f62335 Mon Sep 17 00:00:00 2001 From: Agent Runner Date: Tue, 24 Mar 2026 05:39:34 -0700 Subject: [PATCH] feat: project loop topology into runbook results (#1905) --- scripts/Invoke-IntegrationRunbook.ps1 | 31 +++++++++++++- tests/IntegrationRunbook.Tests.ps1 | 60 +++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 2 deletions(-) diff --git a/scripts/Invoke-IntegrationRunbook.ps1 b/scripts/Invoke-IntegrationRunbook.ps1 index b26aa1dc7..adb3b8ad4 100644 --- a/scripts/Invoke-IntegrationRunbook.ps1 +++ b/scripts/Invoke-IntegrationRunbook.ps1 @@ -257,6 +257,7 @@ function Invoke-PhaseLoop { param($r,$ctx) Write-PhaseBanner $r.name $env:LOOP_SIMULATE = '' # ensure real + $loopFinalStatusPath = Join-Path ([System.IO.Path]::GetTempPath()) ("runbook-loop-final-status-" + [guid]::NewGuid().ToString() + ".json") # Optional quick/override controls via env (non-breaking defaults) try { if (-not $PSBoundParameters.ContainsKey('LoopIterations')) { @@ -270,9 +271,36 @@ function Invoke-PhaseLoop { try { if ($env:RUNBOOK_LOOP_QUICK -match '^(?i:1|true|yes|on)$') { $failOn = $true } } catch {} $env:LOOP_FAIL_ON_DIFF = ($failOn ? 'true' : 'false') try { - & (Join-Path (Get-Location) 'scripts' 'Run-AutonomousIntegrationLoop.ps1') + & (Join-Path (Get-Location) 'scripts' 'Run-AutonomousIntegrationLoop.ps1') -FinalStatusJsonPath $loopFinalStatusPath $code = $LASTEXITCODE $r.details.exitCode = $code + $r.details.finalStatusPath = $loopFinalStatusPath + if (Test-Path -LiteralPath $loopFinalStatusPath -PathType Leaf) { + try { + $finalStatus = Get-Content -LiteralPath $loopFinalStatusPath -Raw | ConvertFrom-Json -ErrorAction Stop + $r.details.loopIterations = $finalStatus.iterations + if ($finalStatus.PSObject.Properties.Name -contains 'harness' -and $finalStatus.harness) { + $r.details.harness = $finalStatus.harness + $r.details.executionTopology = [ordered]@{ + runtimeSurface = $finalStatus.harness.runtimeSurface + processModelClass = $finalStatus.harness.processModelClass + windowsOnly = $finalStatus.harness.windowsOnly + requestedSimultaneous = $finalStatus.harness.requestedSimultaneous + cellClass = $finalStatus.harness.cellClass + executionCellLeasePath = $finalStatus.harness.executionCellLeasePath + executionCellId = $finalStatus.harness.executionCellId + executionCellLeaseId = $finalStatus.harness.executionCellLeaseId + harnessInstanceLeasePath = $finalStatus.harness.harnessInstanceLeasePath + harnessInstanceLeaseId = $finalStatus.harness.harnessInstanceLeaseId + harnessInstanceId = $finalStatus.harness.harnessInstanceId + } + } + } catch { + $r.details.finalStatusReadError = $_.Exception.Message + } + } else { + $r.details.finalStatusMissing = $true + } if ($code -eq 0) { $r.status='Passed' } else { $r.status='Failed' } } catch { $r.details.error = $_.Exception.Message @@ -387,4 +415,3 @@ if ($env:GITHUB_STEP_SUMMARY) { if ($PassThru) { return $final } if ($overallFailed) { exit 1 } else { exit 0 } - diff --git a/tests/IntegrationRunbook.Tests.ps1 b/tests/IntegrationRunbook.Tests.ps1 index 418995c6b..bd4175e2b 100644 --- a/tests/IntegrationRunbook.Tests.ps1 +++ b/tests/IntegrationRunbook.Tests.ps1 @@ -115,6 +115,66 @@ Describe 'IntegrationRunbook - Phase Selection & JSON' -Tag 'Unit' { Remove-Item Env:LV_HEAD_VI -ErrorAction SilentlyContinue } } + + It 'projects loop execution topology from the loop final status into Loop phase details' { + $tempRepo = Join-Path $TestDrive 'runbook-loop-topology' + $scriptsDir = Join-Path $tempRepo 'scripts' + New-Item -ItemType Directory -Path $scriptsDir -Force | Out-Null + + $runbookCopy = Join-Path $scriptsDir 'Invoke-IntegrationRunbook.ps1' + Copy-Item -LiteralPath $runScript -Destination $runbookCopy -Force + + $loopStub = @" +param([string]`$FinalStatusJsonPath) +`$payload = [ordered]@{ + schema = 'loop-final-status-v1' + timestamp = '2026-03-24T12:30:00Z' + iterations = 2 + diffs = 0 + errors = 0 + succeeded = `$true + harness = [ordered]@{ + runtimeSurface = 'windows-native-teststand' + processModelClass = 'parallel-process-model' + windowsOnly = `$true + requestedSimultaneous = `$true + cellClass = 'worker' + executionCellLeasePath = 'tests/results/_agent/runtime/execution-cell-hooke-loop.json' + executionCellId = 'exec-cell-hooke-loop-01' + executionCellLeaseId = 'lease-hooke-loop-01' + harnessInstanceLeasePath = 'tests/results/_agent/runtime/harness-instance-hooke-loop.json' + harnessInstanceLeaseId = 'harness-lease-hooke-loop-01' + harnessInstanceId = 'ts-hooke-loop-01' + } +} +(`$payload | ConvertTo-Json -Depth 8) | Set-Content -LiteralPath `$FinalStatusJsonPath -Encoding UTF8 +exit 0 +"@ + Set-Content -LiteralPath (Join-Path $scriptsDir 'Run-AutonomousIntegrationLoop.ps1') -Value $loopStub -Encoding UTF8 + + Set-Content -LiteralPath (Join-Path $tempRepo 'VI1.vi') -Value '' -Encoding utf8 + Set-Content -LiteralPath (Join-Path $tempRepo 'VI2.vi') -Value '' -Encoding utf8 + + $tmp = Join-Path $tempRepo 'tmp-runbook-loop-topology.json' + Push-Location $tempRepo + try { + & $runbookCopy -Phases 'Prereqs,ViInputs,Loop' -JsonReport $tmp | Out-Null + $LASTEXITCODE | Should -Be 0 + } finally { + Pop-Location + } + + Test-Path $tmp | Should -BeTrue + $json = Get-Content $tmp -Raw | ConvertFrom-Json + $loopPhase = $json.phases | Where-Object name -eq 'Loop' + $loopPhase.status | Should -Be 'Passed' + $loopPhase.details.loopIterations | Should -Be 2 + $loopPhase.details.executionTopology.runtimeSurface | Should -Be 'windows-native-teststand' + $loopPhase.details.executionTopology.processModelClass | Should -Be 'parallel-process-model' + $loopPhase.details.executionTopology.executionCellLeaseId | Should -Be 'lease-hooke-loop-01' + $loopPhase.details.executionTopology.harnessInstanceLeaseId | Should -Be 'harness-lease-hooke-loop-01' + $loopPhase.details.executionTopology.harnessInstanceId | Should -Be 'ts-hooke-loop-01' + } } Describe 'IntegrationRunbook - Schema Shape Minimal Validation' -Tag 'Unit' {