# Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. name: Windows baseline on: schedule: - cron: '17 6 * * *' workflow_dispatch: inputs: full_storage: description: 'Run full packages/storage suite on Windows (slow, evidence lane)' type: boolean default: false concurrency: group: windows-baseline-${{ github.ref }} permissions: contents: read jobs: windows_baseline: name: windows_baseline (non-blocking) runs-on: windows-latest timeout-minutes: 45 continue-on-error: true env: WINDOWS_BASELINE_LOG_DIR: artifacts/windows-baseline steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: '24' cache: npm - name: Create log directory shell: pwsh run: New-Item -ItemType Directory -Force $env:WINDOWS_BASELINE_LOG_DIR | Out-Null - name: Capture process baseline shell: pwsh run: | $processes = @( Get-CimInstance Win32_Process | Where-Object { $_.Name -in @('node.exe', 'electron.exe') } | ForEach-Object { [pscustomobject]@{ ProcessId = $_.ProcessId ParentProcessId = $_.ParentProcessId Name = $_.Name CreationDate = $_.CreationDate.ToUniversalTime().ToString('o') CommandLine = $_.CommandLine } } ) [pscustomobject]@{ CapturedAtUtc = [DateTime]::UtcNow.ToString('o') Processes = $processes } | ConvertTo-Json -Depth 4 | Set-Content "$env:WINDOWS_BASELINE_LOG_DIR/process-baseline.json" - id: install name: Install dependencies continue-on-error: true shell: pwsh run: | npm.cmd ci *> "$env:WINDOWS_BASELINE_LOG_DIR/install.log" $exitCode = $LASTEXITCODE Get-Content "$env:WINDOWS_BASELINE_LOG_DIR/install.log" exit $exitCode - id: build name: Build test artifacts if: always() && steps.install.outcome == 'success' continue-on-error: true shell: pwsh run: | npm.cmd run build:test *> "$env:WINDOWS_BASELINE_LOG_DIR/build.log" $exitCode = $LASTEXITCODE Get-Content "$env:WINDOWS_BASELINE_LOG_DIR/build.log" exit $exitCode - id: smoke name: Run CLI and Electron startup smoke if: always() && steps.build.outcome == 'success' continue-on-error: true shell: pwsh run: | npm.cmd --workspace @maka/desktop run build:smoke *> "$env:WINDOWS_BASELINE_LOG_DIR/smoke-build.log" $exitCode = $LASTEXITCODE Get-Content "$env:WINDOWS_BASELINE_LOG_DIR/smoke-build.log" if ($exitCode -ne 0) { exit $exitCode } npm.cmd run smoke:windows:dist *> "$env:WINDOWS_BASELINE_LOG_DIR/smoke.log" $exitCode = $LASTEXITCODE Get-Content "$env:WINDOWS_BASELINE_LOG_DIR/smoke.log" exit $exitCode - id: runtime_pty_input name: Run Runtime PTY input gates if: always() && steps.build.outcome == 'success' continue-on-error: true timeout-minutes: 5 shell: pwsh run: | node.exe --test --test-force-exit --test-timeout=15000 --test-reporter=tap --test-concurrency=1 --test-name-pattern="semantic text and Enter actions|terminal mode parsed before the control cut|preserves the requested cwd through a real Git Bash login PTY" packages/runtime/dist/__tests__/shell-run-manager.test.js 2>&1 | Tee-Object -FilePath "$env:WINDOWS_BASELINE_LOG_DIR/runtime-pty-input.log" $exitCode = $LASTEXITCODE $output = Get-Content "$env:WINDOWS_BASELINE_LOG_DIR/runtime-pty-input.log" if ($exitCode -eq 0 -and ($output -notcontains '# tests 3' -or $output -notcontains '# pass 3')) { Write-Error 'Runtime PTY input gate did not run exactly three passing tests' exit 1 } exit $exitCode - id: runtime_powershell_encoding name: Run Runtime PowerShell UTF-8 gates if: always() && steps.build.outcome == 'success' continue-on-error: true timeout-minutes: 5 shell: pwsh run: | node.exe --test --test-force-exit --test-timeout=15000 --test-reporter=tap --test-concurrency=1 --test-name-pattern="preserves CJK PowerShell output through pipes|preserves CJK PowerShell output through a real PTY" packages/runtime/dist/__tests__/shell-run-manager.test.js 2>&1 | Tee-Object -FilePath "$env:WINDOWS_BASELINE_LOG_DIR/runtime-powershell-utf8.log" $exitCode = $LASTEXITCODE $output = Get-Content "$env:WINDOWS_BASELINE_LOG_DIR/runtime-powershell-utf8.log" if ($exitCode -eq 0 -and ($output -notcontains '# tests 2' -or $output -notcontains '# pass 2')) { Write-Error 'Runtime PowerShell UTF-8 gate did not run exactly two passing tests' exit 1 } exit $exitCode # Full packages/storage test:dist is ~10 minutes on windows-latest and # mostly duplicates the Linux unit lane. Baseline keeps process/path/ # lock-sensitive gates here. Crash and owner-death evidence belongs to # windows-recovery.yml, so this diagnostic lane does not duplicate it. # Bump concurrency carefully — several suites spawn child processes and # fight for disk under high fan-out. - id: storage name: Run Windows storage path and lock gates if: always() && steps.build.outcome == 'success' continue-on-error: true timeout-minutes: 12 shell: pwsh run: | $storageGates = @( 'packages/storage/dist/__tests__/root-authority.test.js', 'packages/storage/dist/__tests__/sqlite-recovery-concurrency.test.js', 'packages/storage/dist/__tests__/artifact-writer-lock.test.js', 'packages/storage/dist/__tests__/write-queue.test.js', 'packages/storage/dist/__tests__/marker-file.test.js', 'packages/storage/dist/__tests__/workspace-root.test.js', 'packages/storage/dist/__tests__/workspace-identity.test.js' ) node.exe --test --test-concurrency=2 --test-force-exit --test-timeout=60000 ` @storageGates ` 2>&1 | Tee-Object -FilePath "$env:WINDOWS_BASELINE_LOG_DIR/storage.log" exit $LASTEXITCODE # Full suite is evidence-only and requires an explicit manual opt-in. # Linux unit already covers the package, while the full Windows wall # clock is ~10 minutes. - id: storage_full name: Capture full storage suite baseline if: always() && steps.build.outcome == 'success' && (github.event_name == 'schedule' || inputs.full_storage) continue-on-error: true timeout-minutes: 25 shell: pwsh run: | node.exe scripts/run-workspace-tests-parallel.mjs --concurrency=1 --workspaces=packages/storage *> "$env:WINDOWS_BASELINE_LOG_DIR/storage-full.log" $exitCode = $LASTEXITCODE Get-Content "$env:WINDOWS_BASELINE_LOG_DIR/storage-full.log" exit $exitCode - id: processes name: Audit residual job processes if: always() continue-on-error: true shell: pwsh run: | . ./scripts/windows-process-identity.ps1 $baseline = Get-Content "$env:WINDOWS_BASELINE_LOG_DIR/process-baseline.json" -Raw | ConvertFrom-Json $baselineKeys = [System.Collections.Generic.HashSet[string]]::new( [System.StringComparer]::Ordinal ) foreach ($process in @($baseline.Processes)) { [void]$baselineKeys.Add((Get-WindowsProcessIdentityKey $process)) } $allProcesses = @( Get-CimInstance Win32_Process | ForEach-Object { [pscustomobject]@{ ProcessId = $_.ProcessId ParentProcessId = $_.ParentProcessId Name = $_.Name CreationDate = $_.CreationDate.ToUniversalTime().ToString('o') CommandLine = $_.CommandLine } } ) $leaks = @( $allProcesses | Where-Object { $_.Name -in @('node.exe', 'electron.exe') -and -not $baselineKeys.Contains((Get-WindowsProcessIdentityKey $_)) } ) ConvertTo-Json -InputObject $leaks -Depth 3 | Set-Content "$env:WINDOWS_BASELINE_LOG_DIR/residual-processes.json" if ($leaks.Count -eq 0) { Write-Output 'No residual job-created Node or Electron processes.' exit 0 } $treeProcessIds = [System.Collections.Generic.HashSet[int]]::new() foreach ($process in $leaks) { [void]$treeProcessIds.Add($process.ProcessId) } do { $addedDescendant = $false foreach ($process in $allProcesses) { if ( $treeProcessIds.Contains($process.ParentProcessId) -and $treeProcessIds.Add($process.ProcessId) ) { $addedDescendant = $true } } } while ($addedDescendant) $ownedTree = @( $allProcesses | Where-Object { $treeProcessIds.Contains($_.ProcessId) } ) ConvertTo-Json -InputObject $ownedTree -Depth 3 | Set-Content "$env:WINDOWS_BASELINE_LOG_DIR/residual-process-tree.json" $leaks | Format-List | Out-String | Tee-Object -FilePath "$env:WINDOWS_BASELINE_LOG_DIR/residual-processes.log" foreach ($process in $leaks) { & taskkill.exe /PID $process.ProcessId /T /F 2>&1 | Add-Content "$env:WINDOWS_BASELINE_LOG_DIR/process-cleanup.log" } Start-Sleep -Seconds 1 $remainingById = @{} Get-CimInstance Win32_Process | ForEach-Object { $key = Get-WindowsProcessIdentityKey $_ $remainingById[$key] = $_ } $unreaped = @( $ownedTree | Where-Object { $remainingById.ContainsKey((Get-WindowsProcessIdentityKey $_)) } ) ConvertTo-Json -InputObject $unreaped -Depth 3 | Set-Content "$env:WINDOWS_BASELINE_LOG_DIR/residual-processes-after-cleanup.json" if ($unreaped.Count -gt 0) { Write-Error "Failed to reap $($unreaped.Count) process(es) from the residual job tree." exit 1 } Write-Error "Found $($leaks.Count) residual job root(s) and reaped their $($ownedTree.Count)-process tree." exit 1 - name: Publish baseline summary if: always() shell: pwsh run: | @" ## Windows baseline This lane is intentionally non-blocking while the Phase 1 backlog is being reduced. | Check | Outcome | |---|---| | Install | ${{ steps.install.outcome }} | | Build | ${{ steps.build.outcome }} | | CLI / Electron smoke | ${{ steps.smoke.outcome }} | | Storage path/lock gates | ${{ steps.storage.outcome }} | | Runtime PTY input | ${{ steps.runtime_pty_input.outcome }} | | Runtime PowerShell UTF-8 | ${{ steps.runtime_powershell_encoding.outcome }} | | Full storage suite (nightly or manual opt-in) | ${{ steps.storage_full.outcome }} | | Residual processes | ${{ steps.processes.outcome }} | Download the `windows-baseline` artifact for complete logs. "@ >> $env:GITHUB_STEP_SUMMARY - name: Upload Windows baseline logs if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: windows-baseline path: artifacts/windows-baseline if-no-files-found: warn retention-days: 14