Skip to content

#883 Remove standard PR validation approval gate (#884) #525

#883 Remove standard PR validation approval gate (#884)

#883 Remove standard PR validation approval gate (#884) #525

name: CI Orchestrated (deterministic chain)
on:
push:
branches: [develop]
workflow_dispatch:
inputs:
origin_kind:
description: Trigger origin (e.g., issue_comment)
required: false
type: string
origin_pr:
description: PR number when triggered from a comment
required: false
type: string
origin_comment_id:
description: Comment id when triggered from a comment
required: false
type: string
origin_comment_url:
description: Comment URL when triggered from a comment
required: false
type: string
origin_author:
description: Comment author login
required: false
type: string
strategy:
description: Execution strategy (matrix or single)
required: false
default: single
type: choice
options: [matrix, single]
include_integration:
description: Include Integration-tagged tests
required: false
default: 'true'
type: choice
options: ['true', 'false']
sample_id:
description: Sampling correlation id (prevents cancels)
required: false
type: string
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.event.inputs.sample_id || github.ref }}
cancel-in-progress: true
jobs:
normalize:
runs-on: ubuntu-latest
env:
RESULTS_DIR: tests/results/normalize
outputs:
include_integration: ${{ steps.b.outputs.normalized }}
steps:
- uses: actions/checkout@v5
- name: Wire Probe (J1)
if: '${{ vars.WIRE_PROBES != ''0'' }}'
uses: ./.github/actions/wire-probe
with:
phase: J1
results-dir: tests/results
- name: Wire Probe (J2)
if: '${{ vars.WIRE_PROBES != ''0'' }}'
uses: ./.github/actions/wire-probe
with:
phase: J2
results-dir: tests/results
- name: Configure Git safe.directory (service)
shell: pwsh
run: git config --global --add safe.directory $env:GITHUB_WORKSPACE
- name: Wire Invoker (start)
if: ${{ vars.WIRE_PROBES != '0' }}
uses: ./.github/actions/wire-invoker-start
with:
results-dir: ${{ env.RESULTS_DIR }}
- name: Ensure Invoker (start)
uses: ./.github/actions/ensure-invoker
with:
mode: start
timeout-seconds: '60'
retries: '1'
retry-delay-seconds: '0'
results-dir: ${{ env.RESULTS_DIR }}
require-invoker: 'false'
- name: Normalize include_integration
id: b
uses: ./.github/actions/bool-normalize
with:
value: ${{ inputs.include_integration || 'true' }}
- name: Write run provenance
shell: pwsh
env:
EV_ORIGIN_KIND: ${{ inputs.origin_kind || '' }}
EV_ORIGIN_PR: ${{ inputs.origin_pr || '' }}
EV_ORIGIN_COMMENT_ID: ${{ inputs.origin_comment_id || '' }}
EV_ORIGIN_COMMENT_URL: ${{ inputs.origin_comment_url || '' }}
EV_ORIGIN_AUTHOR: ${{ inputs.origin_author || '' }}
EV_SAMPLE_ID: ${{ inputs.sample_id || '' }}
EV_INCLUDE_INTEGRATION: ${{ inputs.include_integration || 'true' }}
EV_STRATEGY: ${{ inputs.strategy || 'matrix' }}
run: |
pwsh -NoLogo -NoProfile -File tools/Write-RunProvenance.ps1 -ResultsDir '${{ env.RESULTS_DIR }}' -AppendStepSummary
- name: Upload run provenance
if: always()
uses: actions/upload-artifact@v5
with:
name: orchestrated-provenance
path: ${{ env.RESULTS_DIR }}/provenance.json
lint:
runs-on: ubuntu-latest
env:
RESULTS_DIR: tests/results/lint
ACTIONLINT_VERSION: '${{ vars.ACTIONLINT_VERSION || ''1.7.7'' }}'
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
COMPAREVI_TOOLS_IMAGE: ghcr.io/labview-community-ci-cd/comparevi-tools:latest
steps:
- uses: actions/checkout@v5
- name: Wire Probe (J1)
if: '${{ vars.WIRE_PROBES != ''0'' }}'
uses: ./.github/actions/wire-probe
with:
phase: J1
results-dir: tests/results
- name: Wire Probe (J2)
if: '${{ vars.WIRE_PROBES != ''0'' }}'
uses: ./.github/actions/wire-probe
with:
phase: J2
results-dir: tests/results
- name: Install actionlint (retry)
shell: bash
run: |
set -euo pipefail
mkdir -p ./bin
ver="${ACTIONLINT_VERSION:-1.7.7}"
for i in 1 2 3; do
if curl -fsSL https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash | bash -s -- "$ver" ./bin; then
break
else
echo "retry $i"; sleep 2
fi
done
- name: Run actionlint
run: |
./bin/actionlint -color
- name: Configure Git safe.directory (service)
shell: pwsh
run: git config --global --add safe.directory $env:GITHUB_WORKSPACE
- name: Non-LabVIEW checks (Docker)
shell: pwsh
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch || github.event.pull_request.base.repo.default_branch || github.event.workflow_run.repository.default_branch || '' }}
run: |
$params = @()
$defaultBranch = $env:DEFAULT_BRANCH
if ('${{ github.ref_name }}' -eq $defaultBranch -or '${{ github.base_ref }}' -eq $defaultBranch) {
$params += '-FailOnWorkflowDrift'
}
$params += '-SkipDotnetCliBuild'
# Skip linting this workflow while it is executing to avoid orchestration deadlocks.
$params += '-ExcludeWorkflowPaths'
$params += '.github/workflows/ci-orchestrated.yml'
pwsh -File ./tools/Run-NonLVChecksInDocker.ps1 @params
- name: Publish comparevi-cli (SDK container)
if: ${{ hashFiles('src/CompareVi.Tools.Cli/CompareVi.Tools.Cli.csproj') != '' }}
shell: bash
run: |
set -euo pipefail
SHA="${GITHUB_SHA:-local}"
docker run --rm -v "${PWD}:/work" -w /work -e BUILD_SHA="$SHA" mcr.microsoft.com/dotnet/sdk:8.0 bash -lc '
set -euo pipefail
export DOTNET_CLI_HOME=/tmp/dotnet
rm -rf dist/comparevi-cli src/CompareVi.Tools.Cli/obj src/CompareVi.Shared/obj || true
IV="${BUILD_SHA:-local}"
dotnet publish src/CompareVi.Tools.Cli/CompareVi.Tools.Cli.csproj -c Release -nologo -o dist/comparevi-cli -p:UseAppHost=false -p:InformationalVersion="$IV"
'
- name: Validate comparevi-cli outputs
if: ${{ hashFiles('dist/comparevi-cli/comparevi-cli.dll') != '' }}
run: node tools/npm/run-script.mjs cli:validate
- name: Watch run (REST sanity)
if: ${{ hashFiles('dist/tools/watchers/orchestrated-watch.js') != '' }}
run: node dist/tools/watchers/orchestrated-watch.js --run-id ${{ github.run_id }} --poll-ms 1000 --out "${{ env.RESULTS_DIR }}/watcher-rest.json"
- name: Merge REST watcher into session index
if: ${{ hashFiles(format('{0}/watcher-rest.json', env.RESULTS_DIR)) != '' }}
shell: pwsh
run: pwsh -File tools/Update-SessionIndexWatcher.ps1 -ResultsDir "${{ env.RESULTS_DIR }}" -WatcherJson "${{ env.RESULTS_DIR }}/watcher-rest.json"
- name: Compute comparevi-cli checksums
if: ${{ hashFiles('dist/comparevi-cli/**') != '' }}
shell: pwsh
run: |
$dir = 'dist/comparevi-cli'
$outDir = 'tests/results/_cli'
New-Item -ItemType Directory -Force -Path $outDir | Out-Null
$files = Get-ChildItem -LiteralPath $dir -File -Recurse
$list = @()
foreach($f in $files){
$sha = pwsh -File tools/Get-FileSha256.ps1 -Path $f.FullName
$list += [pscustomobject]@{ path = (Resolve-Path $f.FullName).Path; sha256 = $sha; bytes = $f.Length }
}
$meta = [pscustomobject]@{
gitSha = '${{ github.sha }}'
branch = '${{ github.ref_name }}'
generatedAt = (Get-Date).ToString('o')
files = $list
}
$json = $meta | ConvertTo-Json -Depth 6
$json | Out-File -FilePath (Join-Path $outDir 'meta.json') -Encoding utf8
if ($env:GITHUB_STEP_SUMMARY) {
$lines = @('### comparevi-cli artifact','',"- Files: $($files.Count)")
$first = $list | Select-Object -First 1
if ($first) { $lines += ("- First file: {0} (sha256={1})" -f $first.path,$first.sha256) }
$lines -join "`n" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8
}
- name: Upload comparevi-cli artifact
if: ${{ hashFiles('dist/comparevi-cli/**') != '' }}
uses: actions/upload-artifact@v5
with:
name: comparevi-cli-${{ runner.os }}
path: dist/comparevi-cli/**/*
- name: Setup Node with cache
uses: actions/setup-node@v5
with:
node-version: "20"
cache: "npm"
- name: Install CLI validation deps
run: node tools/npm/cli.mjs ci
- name: Install markdownlint-cli (retry)
shell: bash
run: |
set -euo pipefail
for i in 1 2 3; do
if node tools/npm/cli.mjs install -g markdownlint-cli; then
break
else
node tools/npm/cli.mjs cache clean --force || true
echo "retry $i"
sleep 2
fi
done
- name: Run markdownlint (non-blocking)
run: |
markdownlint "**/*.md" --ignore node_modules
continue-on-error: true
preflight:
runs-on: windows-latest
timeout-minutes: 3
needs: [normalize]
env:
RESULTS_DIR: tests/results/preflight
# Hosted Windows health gate before self-hosted jobs
steps:
- uses: actions/checkout@v5
- name: Wire Probe (J1)
if: '${{ vars.WIRE_PROBES != ''0'' }}'
uses: ./.github/actions/wire-probe
with:
phase: J1
results-dir: tests/results
- name: Wire Probe (J2)
if: '${{ vars.WIRE_PROBES != ''0'' }}'
uses: ./.github/actions/wire-probe
with:
phase: J2
results-dir: tests/results
- name: Configure Git safe.directory (service)
shell: pwsh
run: git config --global --add safe.directory $env:GITHUB_WORKSPACE
- name: Runner health (notice-only, hosted)
if: ${{ vars.RUNNER_HEALTH != '0' }}
shell: pwsh
run: pwsh -File tools/Collect-RunnerHealth.ps1 -AppendSummary -EmitJson
- name: Verify Windows runner and idle LabVIEW (surface LVCompare notice)
shell: pwsh
run: |-
Write-Host "Runner: $([System.Environment]::OSVersion.VersionString)"
Write-Host "Pwsh: $($PSVersionTable.PSVersion)"
$cli = 'C:\Program Files\National Instruments\Shared\LabVIEW Compare\LVCompare.exe'
if (-not (Test-Path -LiteralPath $cli)) {
Write-Host "::notice::LVCompare.exe not found at canonical path: $cli (hosted preflight)"
} else { Write-Host "LVCompare present: $cli" }
$lv = Get-Process -Name 'LabVIEW' -ErrorAction SilentlyContinue
if ($lv) { $pids = ($lv | ForEach-Object Id); $msg = "::error::LabVIEW.exe is running (PID(s): {0})" -f ([string]::Join(",", $pids)); Write-Host $msg; exit 1 }
Write-Host 'Preflight OK: Windows runner healthy; LabVIEW not running.'
if ($env:GITHUB_STEP_SUMMARY) {
$note = @('Note:', '- This preflight runs on hosted Windows (windows-latest); LVCompare presence is not required here.', '- Self-hosted Windows steps later in this workflow enforce LVCompare at the canonical path.') -join "`n"
$note | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8
}
# Hosted gate is informational only; enforce LVCompare on self-hosted jobs.
probe:
# Quick interactivity probe on self-hosted Windows to decide single vs fallback
if: ${{ inputs.strategy == 'single' || vars.ORCH_STRATEGY == 'single' }}
runs-on: [self-hosted, Windows, X64]
timeout-minutes: 2
needs: [normalize, preflight]
env:
RESULTS_DIR: tests/results/probe
outputs:
ok: ${{ steps.out.outputs.ok }}
steps:
- uses: actions/checkout@v5
- name: Wire Probe (J1)
if: '${{ vars.WIRE_PROBES != ''0'' }}'
uses: ./.github/actions/wire-probe
with:
phase: J1
results-dir: tests/results
- name: Wire Probe (J2)
if: '${{ vars.WIRE_PROBES != ''0'' }}'
uses: ./.github/actions/wire-probe
with:
phase: J2
results-dir: tests/results
- name: Configure Git safe.directory (service)
shell: pwsh
run: git config --global --add safe.directory $env:GITHUB_WORKSPACE
- name: Runner health (notice-only, self-hosted)
if: ${{ vars.RUNNER_HEALTH != '0' }}
shell: pwsh
run: pwsh -File tools/Collect-RunnerHealth.ps1 -ResultsDir '${{ env.RESULTS_DIR }}' -AppendSummary -EmitJson
- name: Run interactivity probe
id: out
shell: pwsh
run: |
pwsh -File tools/Write-InteractivityProbe.ps1
$ui = [System.Environment]::UserInteractive
$in = $false; try { $in = [Console]::IsInputRedirected } catch {}
$ok = ($ui -and -not $in)
"ok=$ok" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
pester-category:
if: ${{ inputs.strategy == 'matrix' || vars.ORCH_STRATEGY == 'matrix' || (inputs.strategy == '' && vars.ORCH_STRATEGY == '') || (inputs.strategy == 'single' && needs.probe.outputs.ok == 'false') }}
runs-on: [self-hosted, Windows, X64]
timeout-minutes: 3
needs: [normalize, preflight, probe]
strategy:
matrix:
category: [dispatcher, fixtures, schema, comparevi, loop, psummary, workflow]
max-parallel: 1
env:
RESULTS_ROOT: tests/results
RESULTS_DIR: ${{ format('tests/results/{0}', matrix.category) }}
DETECT_LEAKS: '1'
CLEAN_AFTER: '0'
SCAN_ARTIFACTS: '1'
UNBLOCK_GUARD: '0'
# Recommended LabVIEW environment toggles for headless invocations
LV_SUPPRESS_UI: '1'
LV_NO_ACTIVATE: '1'
LV_CURSOR_RESTORE: '1'
LV_IDLE_WAIT_SECONDS: '2'
LV_IDLE_MAX_WAIT_SECONDS: '5'
WATCH_CONSOLE: '1'
LABVIEW_EXE: C:\\Program Files\\National Instruments\\LabVIEW 2025\\LabVIEW.exe
EMIT_LV_CLOSURE_CRUMBS: '1'
steps:
- uses: actions/checkout@v5
- name: Wire Probe (J1)
if: '${{ vars.WIRE_PROBES != ''0'' }}'
uses: ./.github/actions/wire-probe
with:
phase: J1
results-dir: tests/results/${{ matrix.category }}
- name: Wire Probe (J2)
if: '${{ vars.WIRE_PROBES != ''0'' }}'
uses: ./.github/actions/wire-probe
with:
phase: J2
results-dir: tests/results/${{ matrix.category }}
- name: Configure Git safe.directory (service)
shell: pwsh
run: git config --global --add safe.directory $env:GITHUB_WORKSPACE
- name: Append interactivity probe
shell: pwsh
run: pwsh -File tools/Write-InteractivityProbe.ps1
- name: Snapshot processes (pre-invoker)
shell: pwsh
run: |
$procs = Get-Process | Where-Object { $_.Name -in 'pwsh','LVCompare','LabVIEW' } | Select-Object Name,Id,MainWindowTitle
$lines = @('### Pre-Invoker Process Snapshot','')
foreach ($p in $procs) { $lines += ('- {0} (PID {1}) {2}' -f $p.Name,$p.Id,$p.MainWindowTitle) }
if ($env:GITHUB_STEP_SUMMARY) { $lines -join "`n" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8 }
& (Join-Path $PWD 'tools/Emit-LVClosureCrumb.ps1') -ResultsDir '${{ env.RESULTS_DIR }}' -Phase 'pre-invoker'
- name: Wire Invoker (start)
if: ${{ vars.WIRE_PROBES != '0' }}
uses: ./.github/actions/wire-invoker-start
with:
results-dir: ${{ env.RESULTS_DIR }}
- name: Ensure Invoker (start)
id: inv_start_matrix
uses: ./.github/actions/ensure-invoker
with:
mode: start
results-dir: ${{ env.RESULTS_DIR }}
category: ${{ matrix.category }}
# Give the invoker more time and an extra retry while diagnosing READY
timeout-seconds: '120'
retries: '4'
retry-delay-seconds: '7'
- name: Debug invoker markers
if: always()
shell: pwsh
run: |
$dir = "${{ env.RESULTS_DIR }}/_invoker"
if (Test-Path -LiteralPath $dir -PathType Container) {
Write-Host "Invoker marker directory: $dir"
try { Get-ChildItem -LiteralPath $dir | Format-Table -AutoSize | Out-String | Write-Host } catch {}
$boot = Join-Path $dir 'boot.log'
if (Test-Path -LiteralPath $boot -PathType Leaf) {
Write-Host '--- boot.log ---'
Get-Content -LiteralPath $boot -Raw | Write-Host
} else {
Write-Host 'boot.log not found.'
}
} else {
Write-Host '::notice::Invoker marker directory missing.'
}
- name: LabVIEW warmup (preflight; leave LVCompare running)
if: runner.os == 'Windows'
env:
WARMUP_SKIP_ARGS: '1'
shell: pwsh
run: pwsh -File tools/Warmup-LabVIEW.ps1
- name: Snapshot processes (post-invoker)
shell: pwsh
run: |
$procs = Get-Process | Where-Object { $_.Name -in 'pwsh','LVCompare','LabVIEW' } | Select-Object Name,Id,MainWindowTitle
$lines = @('### Post-Invoker Process Snapshot','')
foreach ($p in $procs) { $lines += ('- {0} (PID {1}) {2}' -f $p.Name,$p.Id,$p.MainWindowTitle) }
if ($env:GITHUB_STEP_SUMMARY) { $lines -join "`n" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8 }
& (Join-Path $PWD 'tools/Emit-LVClosureCrumb.ps1') -ResultsDir '${{ env.RESULTS_DIR }}' -Phase 'post-invoker'
- name: Prepare fixture copies (base/head)
if: ${{ steps.inv_start_matrix.outputs.ready == 'true' }}
id: fixtures
uses: ./.github/actions/prepare-fixtures
- name: Export fixture env for tests
shell: pwsh
run: |
"LV_BASE_VI=${{ steps.fixtures.outputs.base }}" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
"LV_HEAD_VI=${{ steps.fixtures.outputs.head }}" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
- name: Apply determinism profile
uses: ./.github/actions/determinism-profile
with:
strict: 'true'
- name: Apply dispatcher profile
id: dprofile
uses: ./.github/actions/dispatcher-profile
with:
emit-failures-json-always: 'true'
detect-leaks: 'true'
fail-on-leaks: 'false'
kill-leaks: 'false'
leak-grace-seconds: '3'
clean-labview-before: 'false'
clean-after: 'false'
track-artifacts: 'true'
- name: Wire Probe (T1)
uses: ./.github/actions/wire-probe
with:
phase: T1
results-dir: ${{ env.RESULTS_DIR }}
- name: Run Compare CLI (category)
if: ${{ steps.inv_start_matrix.outputs.ready == 'true' }}
shell: pwsh
run: |
pwsh -File tools/Invoke-CompareCli.ps1 `
-Category '${{ matrix.category }}' `
-IntegrationMode "${{ needs.normalize.outputs.include_integration == 'true' && 'include' || 'exclude' }}" `
-ResultsRoot '${{ env.RESULTS_ROOT }}'
- name: Summarize CLI output
if: ${{ steps.inv_start_matrix.outputs.ready == 'true' }}
shell: pwsh
run: |
$category = '${{ matrix.category }}'
$cliPath = Join-Path '${{ env.RESULTS_DIR }}' 'cli-run.json'
if (-not (Test-Path -LiteralPath $cliPath -PathType Leaf)) {
"Compare CLI summary not found for category '$category'." | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8
return
}
try {
$cli = Get-Content -LiteralPath $cliPath -Raw | ConvertFrom-Json -ErrorAction Stop
$lines = @(
"### Compare CLI Status ($category)",
"- Status: $($cli.status)",
"- Exit Code: $($cli.exitCode)"
)
if ($cli.summary) {
$lines += ("- Tests: {0} Passed: {1} Failed: {2} Errors: {3}" -f $cli.summary.total,$cli.summary.passed,$cli.summary.failed,$cli.summary.errors)
if ($cli.summary.duration_s) { $lines += ("- Duration (s): {0}" -f $cli.summary.duration_s) }
}
($lines -join "`n") | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8
} catch {
"Compare CLI summary parse failed: $_" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8
}
- name: Ensure session index (fallback)
if: always()
shell: pwsh
run: pwsh -File tools/Ensure-SessionIndex.ps1 -ResultsDir "${{ env.RESULTS_DIR }}" -SummaryJson 'pester-summary.json'
- name: Wire Session Index (S1)
if: ${{ vars.WIRE_PROBES != '0' }}
uses: ./.github/actions/wire-session-index
with:
results-dir: ${{ env.RESULTS_DIR }}
- name: Session index post
if: always()
uses: ./.github/actions/session-index-post
with:
results-dir: ${{ env.RESULTS_DIR }}
validate-schema: true
upload: true
artifact-name: session-index-${{ matrix.category }}
- name: Upload Pester artifacts
if: always()
uses: actions/upload-artifact@v5
with:
name: orchestrated-pester-results-${{ matrix.category }}
path: |
${{ env.RESULTS_DIR }}/**/*.xml
${{ env.RESULTS_DIR }}/**/*.txt
${{ env.RESULTS_DIR }}/pester-summary.json
${{ env.RESULTS_DIR }}/pester-failures.json
${{ env.RESULTS_DIR }}/session-index.json
- name: Upload invoker boot log (category)
if: always() && hashFiles(format('{0}/_invoker/boot.log', env.RESULTS_DIR)) != ''
uses: actions/upload-artifact@v5
with:
name: invoker-boot-${{ matrix.category }}
path: ${{ env.RESULTS_DIR }}/_invoker/boot.log
- name: Wire Guard (pre)
if: ${{ vars.WIRE_PROBES != '0' }}
uses: ./.github/actions/wire-guard-pre
with:
results-dir: ${{ env.RESULTS_DIR }}
- name: Runner Unblock Guard
if: always()
uses: ./.github/actions/runner-unblock-guard
with:
snapshot-path: ${{ env.RESULTS_DIR }}/runner-unblock-snapshot.json
cleanup: ${{ env.UNBLOCK_GUARD == '1' }}
process-names: LabVIEW,LVCompare
- name: Wire Guard (post)
if: ${{ vars.WIRE_PROBES != '0' }}
uses: ./.github/actions/wire-guard-post
with:
results-dir: ${{ env.RESULTS_DIR }}
- name: Ensure Invoker (stop)
if: always()
uses: ./.github/actions/ensure-invoker
with:
mode: stop
results-dir: ${{ env.RESULTS_DIR }}
category: ${{ matrix.category }}
- name: Emit LV closure telemetry (category post-stop)
if: always()
shell: pwsh
run: |
& (Join-Path $PWD 'tools/Emit-LVClosureCrumb.ps1') -ResultsDir '${{ env.RESULTS_DIR }}' -Phase 'post-stop'
- name: Wire Invoker (stop)
if: ${{ vars.WIRE_PROBES != '0' }}
uses: ./.github/actions/wire-invoker-stop
with:
results-dir: ${{ env.RESULTS_DIR }}
- name: Append determinism summary
if: always()
shell: pwsh
run: pwsh -File tools/Write-DeterminismSummary.ps1
- name: Append runner identity
if: always()
shell: pwsh
run: pwsh -File tools/Write-RunnerIdentity.ps1 -SampleId '${{ github.event.inputs.sample_id || '' }}'
- name: Append artifact map
if: always()
shell: pwsh
run: |
$paths = @('${{ env.RESULTS_DIR }}/pester-summary.json','${{ env.RESULTS_DIR }}/pester-results.xml','${{ env.RESULTS_DIR }}/pester-failures.json','${{ env.RESULTS_DIR }}/session-index.json')
$pathsList = $paths -join ';'
pwsh -File tools/Write-ArtifactMap.ps1 -PathsList $pathsList -Title 'Artifacts'
- name: Append top failures
if: always()
shell: pwsh
run: pwsh -File tools/Write-PesterTopFailures.ps1 -ResultsDir "${{ env.RESULTS_DIR }}" -Top 5
- name: Generate dev dashboard (matrix)
if: always()
shell: pwsh
run: pwsh -File tools/Invoke-DevDashboard.ps1 -Group 'pester-selfhosted' -ResultsRoot "${{ env.RESULTS_DIR }}" -OutputRoot "${{ env.RESULTS_DIR }}/dev-dashboard"
- name: Upload dev dashboard (matrix)
if: always()
uses: actions/upload-artifact@v5
with:
name: dev-dashboard-${{ matrix.category }}
path: |
${{ env.RESULTS_DIR }}/dev-dashboard/dashboard.html
${{ env.RESULTS_DIR }}/dev-dashboard/dashboard.json
if-no-files-found: warn
- name: Append dev dashboard summary (matrix)
if: always()
shell: pwsh
run: |
$html = Join-Path "${{ env.RESULTS_DIR }}/dev-dashboard" 'dashboard.html'
$json = Join-Path "${{ env.RESULTS_DIR }}/dev-dashboard" 'dashboard.json'
if ($env:GITHUB_STEP_SUMMARY -and (Test-Path $html)) {
$lines = @(
"### Dev Dashboard (${{ matrix.category }})",
'',
"- HTML: $(Resolve-Path $html)",
"- JSON: $(Resolve-Path $json)"
)
$lines -join "`n" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8
}
- name: Re-run hint
if: always()
shell: pwsh
run: pwsh -File tools/Write-RerunHint.ps1 -Workflow 'ci-orchestrated.yml' -IncludeIntegration '${{ needs.normalize.outputs.include_integration }}' -SampleId '${{ github.event.inputs.sample_id || '' }}'
drift:
if: ${{ inputs.strategy == 'matrix' || vars.ORCH_STRATEGY == 'matrix' || (inputs.strategy == '' && vars.ORCH_STRATEGY == '') }}
runs-on: [self-hosted, Windows, X64]
timeout-minutes: 3
needs: [pester-category]
env:
RESULTS_DIR: results/fixture-drift
INVOKER_REQUIRED: '1'
UNBLOCK_GUARD: '0'
LV_SUPPRESS_UI: '0'
CLEAN_LV_BEFORE: ${{ vars.CLEAN_LV_BEFORE || 'true' }}
CLEAN_LV_AFTER: ${{ vars.CLEAN_LV_AFTER || 'true' }}
CLEAN_LV_INCLUDE_COMPARE: ${{ vars.CLEAN_LV_INCLUDE_COMPARE || vars.CLEAN_LVCOMPARE || 'true' }}
LVCI_SINGLE_COMPARE: '1'
WATCH_CONSOLE: '1'
LABVIEW_EXE: C:\\Program Files\\National Instruments\\LabVIEW 2025\\LabVIEW.exe
EMIT_LV_CLOSURE_CRUMBS: '1'
steps:
- uses: actions/checkout@v5
- name: Wire Probe (J1)
if: '${{ vars.WIRE_PROBES != ''0'' }}'
uses: ./.github/actions/wire-probe
with:
phase: J1
results-dir: results/fixture-drift
- name: Wire Probe (J2)
if: '${{ vars.WIRE_PROBES != ''0'' }}'
uses: ./.github/actions/wire-probe
with:
phase: J2
results-dir: results/fixture-drift
- name: Configure Git safe.directory (service)
shell: pwsh
run: git config --global --add safe.directory $env:GITHUB_WORKSPACE
- name: LabVIEW warmup (best-effort)
if: runner.os == 'Windows'
shell: pwsh
run: pwsh -File tools/Warmup-LabVIEW.ps1
- name: Append interactivity probe
shell: pwsh
run: pwsh -File tools/Write-InteractivityProbe.ps1
- name: Wire Invoker (start)
if: ${{ vars.WIRE_PROBES != '0' }}
uses: ./.github/actions/wire-invoker-start
with:
results-dir: ${{ env.RESULTS_DIR }}
- name: Ensure Invoker (start)
uses: ./.github/actions/ensure-invoker
with:
mode: start
results-dir: ${{ env.RESULTS_DIR }}
- name: Wire Probe (C1)
if: ${{ vars.WIRE_PROBES != '0' }}
uses: ./.github/actions/wire-probe
with:
phase: C1
results-dir: ${{ env.RESULTS_DIR }}
- name: Fixture Drift Orchestrator
uses: ./.github/actions/fixture-drift
with:
render-report: 'true'
comment-on-fail: 'true'
upload-artifacts: 'true'
artifact-name: orchestrated-fixture-drift
- name: Wire Probe (C2)
if: ${{ vars.WIRE_PROBES != '0' }}
uses: ./.github/actions/wire-probe
with:
phase: C2
results-dir: ${{ env.RESULTS_DIR }}
- name: Wire Session Index (S1)
if: ${{ vars.WIRE_PROBES != '0' }}
uses: ./.github/actions/wire-session-index
with:
results-dir: ${{ env.RESULTS_DIR }}
- name: Session index post (best-effort)
if: always()
uses: ./.github/actions/session-index-post
with:
results-dir: ${{ env.RESULTS_DIR }}
validate-schema: true
upload: true
artifact-name: fixture-drift-session-index
- name: Generate dev dashboard (drift)
if: always()
shell: pwsh
run: pwsh -File tools/Invoke-DevDashboard.ps1 -Group 'fixture-drift' -ResultsRoot '${{ env.RESULTS_DIR }}' -OutputRoot '${{ env.RESULTS_DIR }}/dev-dashboard'
- name: Upload dev dashboard (drift)
if: always()
uses: actions/upload-artifact@v5
with:
name: dev-dashboard-drift
path: |
${{ env.RESULTS_DIR }}/dev-dashboard/dashboard.html
${{ env.RESULTS_DIR }}/dev-dashboard/dashboard.json
if-no-files-found: warn
- name: Append dev dashboard summary (drift)
if: always()
shell: pwsh
run: |
$html = Join-Path '${{ env.RESULTS_DIR }}/dev-dashboard' 'dashboard.html'
$json = Join-Path '${{ env.RESULTS_DIR }}/dev-dashboard' 'dashboard.json'
if ($env:GITHUB_STEP_SUMMARY -and (Test-Path $html)) {
$lines = @(
'### Dev Dashboard (drift)',
'',
"- HTML: $(Resolve-Path $html)",
"- JSON: $(Resolve-Path $json)"
)
$lines -join "`n" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8
}
- name: Parse Compare Exec JSON (drift)
if: always()
shell: pwsh
run: |
pwsh -File tools/Parse-CompareExec.ps1 -SearchDir '${{ env.RESULTS_DIR }}' -OutJson '${{ env.RESULTS_DIR }}/compare-outcome.json'
- name: Upload compare outcome (drift)
if: always() && hashFiles(format('{0}/compare-outcome.json', env.RESULTS_DIR)) != ''
uses: actions/upload-artifact@v5
with:
name: compare-outcome-drift
path: ${{ env.RESULTS_DIR }}/compare-outcome.json
- name: Wire Guard (pre)
if: ${{ vars.WIRE_PROBES != '0' }}
uses: ./.github/actions/wire-guard-pre
with:
results-dir: ${{ env.RESULTS_DIR }}
- name: Runner Unblock Guard
if: always()
uses: ./.github/actions/runner-unblock-guard
with:
snapshot-path: ${{ env.RESULTS_DIR }}/runner-unblock-snapshot.json
cleanup: ${{ env.UNBLOCK_GUARD == '1' }}
process-names: LabVIEW,LVCompare
- name: Wire Guard (post)
if: ${{ vars.WIRE_PROBES != '0' }}
uses: ./.github/actions/wire-guard-post
with:
results-dir: ${{ env.RESULTS_DIR }}
- name: Ensure Invoker (stop)
if: always()
uses: ./.github/actions/ensure-invoker
with:
mode: stop
results-dir: ${{ env.RESULTS_DIR }}
- name: Emit LV closure telemetry (drift post-stop)
if: always()
shell: pwsh
run: |
& (Join-Path $PWD 'tools/Emit-LVClosureCrumb.ps1') -ResultsDir '${{ env.RESULTS_DIR }}' -Phase 'post-stop'
- name: Wire Invoker (stop)
if: ${{ vars.WIRE_PROBES != '0' }}
uses: ./.github/actions/wire-invoker-stop
with:
results-dir: ${{ env.RESULTS_DIR }}
publish:
if: ${{ always() && (inputs.strategy == 'matrix' || vars.ORCH_STRATEGY == 'matrix' || (inputs.strategy == '' && vars.ORCH_STRATEGY == '')) && needs.pester-category.result != 'skipped' }}
runs-on: ubuntu-latest
needs: [lint, pester-category, drift]
steps:
- uses: actions/checkout@v5
- name: Wire Probe (J1)
if: '${{ vars.WIRE_PROBES != ''0'' }}'
uses: ./.github/actions/wire-probe
with:
phase: J1
results-dir: tests/results
- name: Wire Probe (J2)
if: '${{ vars.WIRE_PROBES != ''0'' }}'
uses: ./.github/actions/wire-probe
with:
phase: J2
results-dir: tests/results
- name: Configure Git safe.directory (service)
shell: pwsh
run: git config --global --add safe.directory $env:GITHUB_WORKSPACE
- name: Summarize orchestrated run
shell: pwsh
run: |
$lines = @(
'### CI Orchestrated Summary',
'- Lint: ${{ needs.lint.result }}',
'- Pester: ${{ needs.pester-category.result }}',
'- Drift: ${{ needs.drift.result }}'
) -join "`n"
$lines | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8
- name: Setup Node.js (derive env)
if: always()
uses: actions/setup-node@v5
with:
node-version: '20'
- name: Derive environment snapshot (publish)
if: always()
shell: bash
run: |
set -euo pipefail
node tools/npm/run-script.mjs --silent derive:env > derived-env.json
mkdir -p tests/results/_agent
cp derived-env.json tests/results/_agent/derived-env.json
if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then
{
echo '### Derived Environment';
echo '```json';
cat derived-env.json;
echo '```';
} >> "$GITHUB_STEP_SUMMARY"
fi
- name: Append run links (publish)
if: always()
shell: pwsh
env:
GH_TOKEN: ${{ github.token }}
run: |
$summaryPath = $env:GITHUB_STEP_SUMMARY
if (-not $summaryPath) { return }
if (-not $env:GH_TOKEN) {
Write-Warning '[run-links] GH_TOKEN missing; skipping run link summary.'
return
}
$repo = $env:GITHUB_REPOSITORY
if (-not $repo) {
Write-Warning '[run-links] GITHUB_REPOSITORY missing; skipping run link summary.'
return
}
$runId = '${{ github.run_id }}'
$uri = "https://api.github.com/repos/$repo/actions/runs/$runId/jobs?per_page=100"
$headers = @{
Authorization = "Bearer $env:GH_TOKEN"
'X-GitHub-Api-Version' = '2022-11-28'
Accept = 'application/vnd.github+json'
}
try {
$resp = Invoke-RestMethod -Method Get -Uri $uri -Headers $headers -ErrorAction Stop
} catch {
Write-Warning ("[run-links] failed to fetch run jobs: {0}" -f $_.Exception.Message)
return
}
$jobs = @($resp.jobs)
if (-not $jobs -or $jobs.Count -eq 0) { return }
$items = @()
foreach ($job in $jobs) {
if (-not $job.name -or -not $job.html_url) { continue }
$items += ('- [{0}]({1})' -f $job.name, $job.html_url)
}
if ($items.Count -eq 0) { return }
$block = @('### Run Links','') + $items
$block -join "`n" | Out-File -FilePath $summaryPath -Append -Encoding utf8
- name: Wire Probe (P1)
if: ${{ vars.WIRE_PROBES != '0' }}
uses: ./.github/actions/wire-probe
with:
phase: P1
results-dir: tests/results
- name: Download Pester category artifacts (dispatcher)
if: always()
uses: actions/download-artifact@v4
with:
name: orchestrated-pester-results-dispatcher
path: cats/dispatcher
- name: Download Pester category artifacts (fixtures)
if: always()
uses: actions/download-artifact@v4
with:
name: orchestrated-pester-results-fixtures
path: cats/fixtures
- name: Download Pester category artifacts (schema)
if: always()
uses: actions/download-artifact@v4
with:
name: orchestrated-pester-results-schema
path: cats/schema
- name: Download Pester category artifacts (comparevi)
if: always()
uses: actions/download-artifact@v4
with:
name: orchestrated-pester-results-comparevi
path: cats/comparevi
- name: Download Pester category artifacts (loop)
if: always()
uses: actions/download-artifact@v4
with:
name: orchestrated-pester-results-loop
path: cats/loop
- name: Download Pester category artifacts (psummary)
if: always()
uses: actions/download-artifact@v4
with:
name: orchestrated-pester-results-psummary
path: cats/psummary
- name: Download Pester category artifacts (workflow)
if: always()
uses: actions/download-artifact@v4
with:
name: orchestrated-pester-results-workflow
path: cats/workflow
- name: Summarize Pester categories
if: always()
shell: pwsh
run: |
$cats = @('dispatcher','fixtures','schema','comparevi','loop','psummary','workflow')
& (Join-Path $PWD 'tools/Summarize-PesterCategories.ps1') -BaseDir 'cats' -Categories $cats
- name: Download run provenance artifact
if: always()
continue-on-error: true
uses: actions/download-artifact@v4
with:
name: orchestrated-provenance
path: prov
- name: Validate run provenance (schema-lite)
if: always()
shell: pwsh
continue-on-error: true
run: |
$json = Join-Path 'prov' 'provenance.json'
if (Test-Path $json) {
pwsh -File tools/Invoke-JsonSchemaLite.ps1 -JsonPath $json -SchemaPath docs/schemas/run-provenance-v1.schema.json
$status = if ($LASTEXITCODE -eq 0) { 'ok' } else { 'fail' }
if ($env:GITHUB_STEP_SUMMARY) {
$lines = @('### Run Provenance Schema','',"- Status: $status","- File: $json")
$lines -join "`n" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8
}
} else {
if ($env:GITHUB_STEP_SUMMARY) { "### Run Provenance Schema`n- File: (missing) $json" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8 }
}
- name: Validate session-index (categories) schema-lite
if: always()
shell: pwsh
continue-on-error: true
run: |
$cats = @('dispatcher','fixtures','schema','comparevi','loop','psummary','workflow')
$ok=0; $fail=0; $miss=0
foreach ($c in $cats) {
$p1 = Join-Path (Join-Path 'cats' $c) 'tests/results/session-index.json'
$p2 = Join-Path (Join-Path 'cats' $c) 'session-index.json'
$json = if (Test-Path $p1) { $p1 } elseif (Test-Path $p2) { $p2 } else { $null }
if (-not $json) { $miss++; continue }
pwsh -File tools/Invoke-JsonSchemaLite.ps1 -JsonPath $json -SchemaPath docs/schemas/session-index-v1.schema.json
if ($LASTEXITCODE -eq 0) { $ok++ } else { $fail++ }
}
if ($env:GITHUB_STEP_SUMMARY) {
$lines = @('### Session Index Schema (categories)','',"- ok: $ok","- fail: $fail","- missing: $miss")
$lines -join "`n" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8
}
# Single Windows job path (v2 folded in)
- name: Re-run with same inputs
if: ${{ always() }}
shell: pwsh
env:
GH_STRATEGY: '${{ inputs.strategy }}'
GH_INCLUDE: '${{ inputs.include_integration }}'
GH_SAMPLE_ID: '${{ inputs.sample_id }}'
run: |-
$strategy = if ($env:GH_STRATEGY) { $env:GH_STRATEGY } else { 'matrix' }
$include = if ($env:GH_INCLUDE) { $env:GH_INCLUDE } else { 'true' }
$sid = if ($env:GH_SAMPLE_ID) { $env:GH_SAMPLE_ID } else { '<id>' }
$cmd = "/run orchestrated strategy={0} include_integration={1} sample_id={2}" -f $strategy,$include,$sid
$lines = @('### Re-run With Same Inputs','',"$ $cmd")
if ($env:GITHUB_STEP_SUMMARY) { $lines -join "`n" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8 }
- name: Append re-run hint (gh)
if: ${{ always() }}
shell: pwsh
run: pwsh -File tools/Write-RerunHint.ps1 -Workflow 'ci-orchestrated.yml' -IncludeIntegration '${{ inputs.include_integration || 'true' }}' -SampleId '${{ inputs.sample_id || '' }}'
windows-single:
if: ${{ (inputs.strategy == 'single' || vars.ORCH_STRATEGY == 'single') && needs.probe.outputs.ok == 'true' }}
runs-on: [self-hosted, Windows, X64]
timeout-minutes: 10
needs: [normalize, preflight, probe]
env:
RESULTS_DIR: tests/results/single
UNBLOCK_GUARD: '0'
LV_SUPPRESS_UI: '0'
CLEAN_LABVIEW: '0'
CLEAN_AFTER: '0'
DETECT_LEAKS: '1'
FAIL_ON_LEAKS: '0'
KILL_LEAKS: '0'
WATCH_CONSOLE: '1'
INVOKER_REQUIRED: '1'
LABVIEW_EXE: C:\\Program Files\\National Instruments\\LabVIEW 2025\\LabVIEW.exe
EMIT_LV_CLOSURE_CRUMBS: '1'
TRACE_MATRIX: '1'
TRACE_MATRIX_HTML: '1'
steps:
- uses: actions/checkout@v5
- name: Wire Probe (J1)
if: '${{ vars.WIRE_PROBES != ''0'' }}'
uses: ./.github/actions/wire-probe
with:
phase: J1
results-dir: tests/results
- name: Wire Probe (J2)
if: '${{ vars.WIRE_PROBES != ''0'' }}'
uses: ./.github/actions/wire-probe
with:
phase: J2
results-dir: tests/results
- name: Configure Git safe.directory (service)
shell: pwsh
run: git config --global --add safe.directory $env:GITHUB_WORKSPACE
- name: Interactivity Probe (gate)
id: ip
shell: pwsh
run: |
pwsh -File tools/Write-InteractivityProbe.ps1
$ui = [System.Environment]::UserInteractive
$in = $false; try { $in = [Console]::IsInputRedirected } catch {}
$ok = ($ui -and -not $in)
"ok=$ok" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
if (-not $ok -and $env:GITHUB_STEP_SUMMARY) {
$lines = @('### Interactivity Probe','', '- Result: non-interactive (skipping single strategy steps)')
$lines -join "`n" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8
}
- name: Snapshot processes (pre-invoker)
if: ${{ steps.ip.outputs.ok == 'true' }}
shell: pwsh
run: |
$procs = Get-Process | Where-Object { $_.Name -in 'pwsh','LVCompare','LabVIEW' } | Select-Object Name,Id,MainWindowTitle
$lines = @('### Pre-Invoker Process Snapshot','')
foreach ($p in $procs) { $lines += ('- {0} (PID {1}) {2}' -f $p.Name,$p.Id,$p.MainWindowTitle) }
if ($env:GITHUB_STEP_SUMMARY) { $lines -join "`n" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8 }
& (Join-Path $PWD 'tools/Emit-LVClosureCrumb.ps1') -ResultsDir '${{ env.RESULTS_DIR }}' -Phase 'pre-invoker'
- name: Wire Invoker (start)
if: ${{ vars.WIRE_PROBES != '0' }}
uses: ./.github/actions/wire-invoker-start
with:
results-dir: ${{ env.RESULTS_DIR }}
- name: Ensure Invoker (start)
if: ${{ steps.ip.outputs.ok == 'true' }}
id: inv_start_single
continue-on-error: true
uses: ./.github/actions/ensure-invoker
with:
mode: start
results-dir: ${{ env.RESULTS_DIR }}
timeout-seconds: '60'
retries: '1'
retry-delay-seconds: '0'
- name: Warmup LabVIEW (preflight; leave LVCompare running)
if: ${{ steps.ip.outputs.ok == 'true' }}
env:
WARMUP_SKIP_ARGS: '1'
shell: pwsh
run: pwsh -File tools/Warmup-LabVIEW.ps1
- name: Snapshot processes (post-invoker/warmup)
if: ${{ steps.ip.outputs.ok == 'true' }}
shell: pwsh
run: |
$procs = Get-Process | Where-Object { $_.Name -in 'pwsh','LVCompare','LabVIEW' } | Select-Object Name,Id,MainWindowTitle
$lines = @('### Post-Invoker/Warmup Process Snapshot','')
foreach ($p in $procs) { $lines += ('- {0} (PID {1}) {2}' -f $p.Name,$p.Id,$p.MainWindowTitle) }
if ($env:GITHUB_STEP_SUMMARY) { $lines -join "`n" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8 }
& (Join-Path $PWD 'tools/Emit-LVClosureCrumb.ps1') -ResultsDir '${{ env.RESULTS_DIR }}' -Phase 'post-warmup'
- name: Wire Probe (T1)
uses: ./.github/actions/wire-probe
with:
phase: T1
results-dir: ${{ env.RESULTS_DIR }}
- name: Pester categories (serial, deterministic)
if: ${{ steps.ip.outputs.ok == 'true' && steps.inv_start_single.outputs.ready == 'true' }}
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$cats = @(
@{ name='dispatcher'; inc=@('Invoke-PesterTests*.ps1','PesterAvailability.Tests.ps1','NestedDispatcher*.Tests.ps1') },
@{ name='fixtures'; inc=@('Fixtures.*.ps1','FixtureValidation*.ps1','FixtureSummary*.ps1','ViBinaryHandling.Tests.ps1','FixtureValidationDiff.Tests.ps1') },
@{ name='schema'; inc=@('Schema.*.ps1','SchemaLite*.ps1') },
@{ name='comparevi'; inc=@('CompareVI*.ps1','CanonicalCli.Tests.ps1','Args.Tokenization.Tests.ps1') },
@{ name='loop'; inc=@('CompareLoop*.ps1','Run-AutonomousIntegrationLoop*.ps1','LoopMetrics.Tests.ps1','Integration-ControlLoop*.ps1','IntegrationControlLoop*.ps1') },
@{ name='psummary'; inc=@('PesterSummary*.ps1','Write-PesterSummaryToStepSummary*.ps1','AggregationHints*.ps1') },
@{ name='workflow'; inc=@('Workflow*.ps1','On-FixtureValidationFail.Tests.ps1','Watch.FlakyRecovery.Tests.ps1','FunctionShadowing*.ps1','FunctionProxy.Tests.ps1','RunSummary.Tool*.ps1','Action.CompositeOutputs.Tests.ps1','Binding.MinRepro.Tests.ps1','ArtifactTracking*.ps1','Guard.*.Tests.ps1') }
)
foreach ($c in $cats) {
$resDir = Join-Path '${{ env.RESULTS_DIR }}' $c.name
$inc = @($c.inc)
./Invoke-PesterTests.ps1 -TestsPath tests -IntegrationMode "${{ needs.normalize.outputs.include_integration == 'true' && 'include' || 'exclude' }}" -ResultsPath $resDir -EmitFailuresJsonAlways -IncludePatterns $inc
}
- name: Generate traceability matrix (single)
if: ${{ steps.ip.outputs.ok == 'true' && steps.inv_start_single.outputs.ready == 'true' }}
shell: pwsh
run: |
$builder = Join-Path $PWD 'tools/Traceability-Matrix.ps1'
if (Test-Path -LiteralPath $builder) {
& $builder -TestsPath 'tests' -ResultsRoot '${{ env.RESULTS_DIR }}' -RenderHtml -RunId '${{ github.run_id }}'
} else {
Write-Warning "[TraceMatrix] Builder script not found at $builder"
}
- name: Drift (invoker; reuse LabVIEW via -lvpath)
if: ${{ steps.ip.outputs.ok == 'true' && steps.inv_start_single.outputs.ready == 'true' }}
uses: ./.github/actions/fixture-drift
with:
render-report: 'true'
comment-on-fail: 'true'
upload-artifacts: 'true'
artifact-name: orchestrated-single-fixture-drift
report-delay-ms: '150'
- name: Ensure Invoker (stop)
if: always()
uses: ./.github/actions/ensure-invoker
with:
mode: stop
results-dir: ${{ env.RESULTS_DIR }}
- name: Emit LV closure telemetry (single post-stop)
if: always()
shell: pwsh
run: |
& (Join-Path $PWD 'tools/Emit-LVClosureCrumb.ps1') -ResultsDir '${{ env.RESULTS_DIR }}' -Phase 'post-stop'
- name: Wire Invoker (stop)
if: ${{ vars.WIRE_PROBES != '0' }}
uses: ./.github/actions/wire-invoker-stop
with:
results-dir: ${{ env.RESULTS_DIR }}
- name: Wire Session Index (S1)
if: ${{ vars.WIRE_PROBES != '0' }}
uses: ./.github/actions/wire-session-index
with:
results-dir: ${{ env.RESULTS_DIR }}
- name: Session index post (single)
if: always()
uses: ./.github/actions/session-index-post
with:
results-dir: ${{ env.RESULTS_DIR }}
validate-schema: true
upload: true
artifact-name: orchestrated-single-session-index
- name: Runner Unblock Guard (single)
if: always()
uses: ./.github/actions/runner-unblock-guard
with:
snapshot-path: ${{ env.RESULTS_DIR }}/runner-unblock-snapshot.json
cleanup: ${{ env.UNBLOCK_GUARD == '1' }}
process-names: conhost,pwsh,LabVIEW,LVCompare
- name: Upload invoker boot log (single)
if: always() && hashFiles(format('{0}/_invoker/boot.log', env.RESULTS_DIR)) != ''
uses: actions/upload-artifact@v5
with:
name: invoker-boot-single
path: ${{ env.RESULTS_DIR }}/_invoker/boot.log
- name: Append final summary (single)
if: always()
shell: pwsh
run: |
$lines = @('### CI Orchestrated (single Windows) summary','')
$lines += '- No cleanup; no UI suppression; LVCompare-only'
$lines += ('- Include Integration: {0}' -f ('${{ needs.normalize.outputs.include_integration }}'))
$lines -join "`n" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8
- name: Generate dev dashboard (single)
if: always()
shell: pwsh
run: pwsh -File tools/Invoke-DevDashboard.ps1 -Group 'pester-selfhosted' -ResultsRoot '${{ env.RESULTS_DIR }}' -OutputRoot '${{ env.RESULTS_DIR }}/dev-dashboard'
- name: Upload dev dashboard (single)
if: always()
uses: actions/upload-artifact@v5
with:
name: dev-dashboard-single
path: |
${{ env.RESULTS_DIR }}/dev-dashboard/dashboard.html
${{ env.RESULTS_DIR }}/dev-dashboard/dashboard.json
if-no-files-found: warn
- name: Upload traceability matrix (single)
if: always() && hashFiles('tests/results/_trace/trace-matrix.json') != ''
uses: actions/upload-artifact@v5
with:
name: traceability-matrix-single
path: |
${{ env.RESULTS_DIR }}/_trace/trace-matrix.json
${{ env.RESULTS_DIR }}/_trace/trace-matrix.html
if-no-files-found: warn
- name: Append dev dashboard summary (single)
if: always()
shell: pwsh
run: |
$html = Join-Path '${{ env.RESULTS_DIR }}/dev-dashboard' 'dashboard.html'
$json = Join-Path '${{ env.RESULTS_DIR }}/dev-dashboard' 'dashboard.json'
if ($env:GITHUB_STEP_SUMMARY -and (Test-Path $html)) {
$lines = @(
'### Dev Dashboard (single)',
'',
"- HTML: $(Resolve-Path $html)",
"- JSON: $(Resolve-Path $json)"
)
$lines -join "`n" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8
}
- name: Wire Probe (P1)
if: ${{ vars.WIRE_PROBES != '0' }}
uses: ./.github/actions/wire-probe
with:
phase: P1
results-dir: ${{ env.RESULTS_DIR }}
- name: Agent wait summary (single)
if: always()
uses: ./.github/actions/agent-wait-post
with:
results-dir: ${{ env.RESULTS_DIR }}
fail-on-outside: 'false'
upload-artifact: 'false'
- name: Send Ctrl+C to lingering consoles (optional)
if: always() && env.SEND_CTRL_C == '1'
shell: pwsh
run: pwsh -File tools/Send-CtrlC.ps1 -Names 'pwsh','conhost' -Max 5
- name: Re-run with same inputs (single)
if: ${{ always() }}
shell: pwsh
env:
GH_STRATEGY: '${{ inputs.strategy }}'
GH_INCLUDE: '${{ inputs.include_integration }}'
GH_SAMPLE_ID: '${{ inputs.sample_id }}'
run: |-
$strategy = if ($env:GH_STRATEGY) { $env:GH_STRATEGY } else { 'single' }
$include = if ($env:GH_INCLUDE) { $env:GH_INCLUDE } else { 'true' }
$sid = if ($env:GH_SAMPLE_ID) { $env:GH_SAMPLE_ID } else { '<id>' }
$cmd = "/run orchestrated strategy={0} include_integration={1} sample_id={2}" -f $strategy,$include,$sid
$lines = @('### Re-run With Same Inputs','',"$ $cmd")
if ($env:GITHUB_STEP_SUMMARY) { $lines -join "`n" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8 }
- name: Append re-run hint (gh; single)
if: ${{ always() }}
shell: pwsh
run: pwsh -File tools/Write-RerunHint.ps1 -Workflow 'ci-orchestrated.yml' -IncludeIntegration '${{ inputs.include_integration || 'true' }}' -SampleId '${{ inputs.sample_id || '' }}'