ci: overlap ARM SSH setup and independent observation waits (#24714)

This commit is contained in:
Neil
2026-10-02 14:00:49 -07:00
committed by GitHub
parent b94c75c4bd
commit cc73c8e1a7
8 changed files with 556 additions and 69 deletions
+20 -5
View File
@@ -84,10 +84,6 @@ jobs:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: ./.github/actions/install-node-dependencies
id: dependencies
with:
native-runtime: node
- name: Self-test the provisioning scripts before touching the machine
shell: pwsh
run: |
@@ -97,6 +93,22 @@ jobs:
if($errors.Count){throw "PowerShell parse failed: $($file.FullName)"}
}
& config/ci/windows-ssh-provider/preview-ssh/test-preview-diagnostics.ps1
# ARM inbox servicing can finish while the independent Node artifacts are prepared.
- name: Prepare the Windows inbox SSH capability
id: inbox-capability
background: true
shell: pwsh
run: |
if('${{ matrix.server }}' -eq 'inbox' -and '${{ matrix.arch }}' -eq 'arm64'){
$receipts=Join-Path $pwd '.build/ssh-windows-host-receipts'
New-Item -ItemType Directory -Force -Path $receipts | Out-Null
. config/ci/windows-ssh-provider/preview-ssh/windows-ssh-capability.ps1
Initialize-WindowsInboxSshCapability -Arch '${{ matrix.arch }}' -Receipt (Join-Path $receipts 'inbox-capability-preparation.json')
}
- uses: ./.github/actions/install-node-dependencies
id: dependencies
with:
native-runtime: node
# The deploy materializes rung A from this template; only this runner's slot exists here.
# The process-tree addon carries the launcher that starts the relay outside sshd's job; the
# orcad slot and the relay both stage it, and a standard-user host has no other launch route.
@@ -122,6 +134,7 @@ jobs:
pnpm build:orcad-prebuilds --require-slots "win32-${{ matrix.arch }}"
node config/scripts/build-orcad-template.mjs --targets "win32-${{ matrix.arch }}"
pnpm run build:relay
- wait: inbox-capability
- name: Run the Windows host cells against a private ${{ matrix.server }} sshd
shell: pwsh
timeout-minutes: 50
@@ -135,6 +148,8 @@ jobs:
$cells=@($env:CELLS -split ',' | ForEach-Object {$_.Trim()} | Where-Object {$_})
if(-not $cells.Count){$cells=@('pinned-cmd','pinned-powershell','legacy-opt-out')}
$archive=''
$preparation=''
if('${{ matrix.server }}' -eq 'inbox' -and '${{ matrix.arch }}' -eq 'arm64'){$preparation=Join-Path $receipts 'inbox-capability-preparation.json'}
if('${{ matrix.server }}' -eq 'preview'){
$archive=Join-Path $env:RUNNER_TEMP 'preview-${{ matrix.archive }}'
# The provisioning script refuses the archive unless its sha256 and every binary's match the pin.
@@ -144,7 +159,7 @@ jobs:
$callback={param($context)
& (Join-Path $tools 'invoke-pinned-relay-cells.ps1') -SourceRoot $sourceRoot -Context $context -Target 'win32-${{ matrix.arch }}' -ReceiptRoot $receipts -Cells $cells
}.GetNewClosure()
& (Join-Path $tools 'preview-ssh/prove-preview-openssh.ps1') -Archive $archive -Arch '${{ matrix.arch }}' -Server '${{ matrix.server }}' -Receipt (Join-Path $receipts 'provider-server.json') -Accounts $cells.Count -HiddenTools @('npm','npx','node-gyp','gcc','g++','cc','c++','make','cl','clang','clang++','msbuild','cmake') -HostCellProbe $callback 2>&1 | Tee-Object (Join-Path $receipts 'provision.log')
& (Join-Path $tools 'preview-ssh/prove-preview-openssh.ps1') -Archive $archive -Arch '${{ matrix.arch }}' -Server '${{ matrix.server }}' -Receipt (Join-Path $receipts 'provider-server.json') -InboxPreparationReceipt $preparation -Accounts $cells.Count -HiddenTools @('npm','npx','node-gyp','gcc','g++','cc','c++','make','cl','clang','clang++','msbuild','cmake') -HostCellProbe $callback 2>&1 | Tee-Object (Join-Path $receipts 'provision.log')
- uses: actions/upload-artifact@v7
if: always()
with:
@@ -2,46 +2,34 @@
# -HiddenTools: the private accounts are denied every machine PATH directory holding one of these
# executables, and their own PATH carries logging shims for them, so SSH sessions have no host toolchain.
# -HostCellProbe receives a context hashtable (accounts, port, keys, shim log) once provisioning passes.
param([Parameter(Mandatory=$true)][string]$Receipt,[string]$Archive,[Parameter(Mandatory=$true)][ValidateSet('arm64','x64')][string]$Arch,[ValidateSet('preview','inbox')][string]$Server='preview',[scriptblock]$ProductionRouteProbe,[ValidateRange(1,4)][int]$Accounts=1,[string[]]$HiddenTools=@(),[scriptblock]$HostCellProbe)
param([Parameter(Mandatory=$true)][string]$Receipt,[string]$Archive,[Parameter(Mandatory=$true)][ValidateSet('arm64','x64')][string]$Arch,[ValidateSet('preview','inbox')][string]$Server='preview',[scriptblock]$ProductionRouteProbe,[ValidateRange(1,4)][int]$Accounts=1,[string[]]$HiddenTools=@(),[scriptblock]$HostCellProbe,[string]$InboxPreparationReceipt)
$ErrorActionPreference = 'Stop'
. (Join-Path $PSScriptRoot 'windows-ssh-capability.ps1')
$target=@{arm64=@{os='Arm64';folder='OpenSSH-ARM64';machine='0xAA64';archive='698c6aec31c1dd0fb996206e8741f4531a97355686b5431ef347d531b07fcd42'};x64=@{os='X64';folder='OpenSSH-Win64';machine='0x8664';archive='23f50f3458c4c5d0b12217c6a5ddfde0137210a30fa870e98b29827f7b43aba5'}}[$Arch]
$scopeServer=if($Server -eq 'inbox'){'Windows inbox OpenSSH.Server capability binaries'}else{'Microsoft Win32-OpenSSH 10.0.0.0p2-Preview'}
$report = @{scope="$scopeServer $Arch private loopback authentication and stock cmd.exe dispatch"; server=$Server; status='running'; imageVersion=$env:ImageVersion; cleanup=@('not-confirmed'); globalBootstrapCleanup='Not qualified: service bootstrap may create ProgramData SSH and OpenSSH registry entries; disposable CI VM destruction is the boundary'; observations=@(); stages=@(); diagnosticCaptureFailures=@()}
$script:receiptWritten=$false
function Write-Stage([string]$Stage) {
$timestamp=[DateTime]::UtcNow.ToString('o')
$report.stages += @{stage=$Stage; utc=$timestamp}
try {
$bytes=[Text.UTF8Encoding]::new($false).GetBytes(($report | ConvertTo-Json -Depth 6))
$temporary="$Receipt.pending"
$stream=[IO.FileStream]::new($temporary,[IO.FileMode]::Create,[IO.FileAccess]::Write,[IO.FileShare]::Read)
try {$stream.Write($bytes,0,$bytes.Length);$stream.Flush($true)} finally {$stream.Dispose()}
[IO.File]::Move($temporary,$Receipt,$true)
$script:receiptWritten=$true
} catch {Write-Warning 'Progress receipt could not be updated; cleanup must still run'}
Write-Host "Native SSH stage: $Stage ($timestamp)"
if(Write-WindowsSshReceiptStage $report $Receipt $Stage){$script:receiptWritten=$true}
}
Write-Stage 'preflight-start'
if(-not $script:receiptWritten){throw 'Initial progress receipt unavailable; refuse provisioning'}
if ($env:GITHUB_ACTIONS -ne 'true' -or $env:ORCA_ISOLATED_SSH_CI -ne '1' -or [Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString() -ne $target.os) { throw "Requires isolated native $Arch GitHub runner" }
$admin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $admin) { throw 'Administrative private service/account setup required' }
Assert-IsolatedWindowsSshCi $Arch
Write-Stage 'existing-server-query-start'
$inboxDir=Join-Path $env:WINDIR 'System32\OpenSSH'
function Assert-GlobalServerDormant {
$global=Get-CimInstance Win32_Service -Filter "Name='sshd'"
if(-not $global){return}
# Inbox mode may register the global service; it must stay stopped and never be started here.
if($Server -ne 'inbox' -or $global.State -ne 'Stopped' -or $global.PathName.Trim('"') -ne (Join-Path $inboxDir 'sshd.exe')){throw 'Refuse an existing global SSH server'}
}
Assert-GlobalServerDormant
Assert-WindowsSshGlobalServerDormant $Server
Write-Stage 'existing-server-query-complete'
Write-Stage 'default-shell-query-start'
$openSshKey = 'HKLM:\SOFTWARE\OpenSSH'
$registry = Get-ItemProperty -LiteralPath $openSshKey -ErrorAction SilentlyContinue
# Host-cell probes may set DefaultShell per cell; cleanup restores this stock state.
if ($registry.DefaultShell -or $registry.DefaultShellCommandOption) { throw 'Requires stock cmd.exe OpenSSH shell at start' }
Assert-WindowsSshStockShell
Write-Stage 'default-shell-query-complete'
if($InboxPreparationReceipt){
if($Server -ne 'inbox'){throw 'Inbox preparation receipt cannot qualify a preview server'}
$preparation=Get-Content -LiteralPath $InboxPreparationReceipt -Raw | ConvertFrom-Json
if($preparation.status -ne 'passed' -or $preparation.arch -ne $Arch -or $preparation.sourceSha -ne $env:GITHUB_SHA -or $preparation.runId -ne $env:GITHUB_RUN_ID -or $preparation.runAttempt -ne $env:GITHUB_RUN_ATTEMPT -or $preparation.runnerName -ne $env:RUNNER_NAME -or $preparation.imageVersion -ne $env:ImageVersion){throw 'Inbox preparation receipt identity or verdict mismatch'}
$report.inboxCapabilityPreparation=$preparation
}
$id = [Guid]::NewGuid().ToString('N').Substring(0,10)
$name = "orca$id"
$accountNames = @($name) + @(if($Accounts -gt 1){2..$Accounts | ForEach-Object {"$name$_"}})
@@ -180,12 +168,7 @@ try {
$report.nativeInputs=$verified
Write-Stage 'preview-native-input-verification-complete'
} else {
Write-Stage 'inbox-capability-start'
$capability=Get-WindowsCapability -Online -Name 'OpenSSH.Server~~~~0.0.1.0'
$report.inboxCapabilityInitialState=[string]$capability.State
if($capability.State -ne 'Installed'){Add-WindowsCapability -Online -Name 'OpenSSH.Server~~~~0.0.1.0' | Out-Null}
Assert-GlobalServerDormant
Write-Stage 'inbox-capability-complete'
Install-WindowsInboxSshCapability $Arch $report {param($stage) Write-Stage $stage}
$verified=@()
foreach($binary in @('sshd.exe','ssh.exe','ssh-keygen.exe','sftp.exe','sftp-server.exe')){
$path=Join-Path $sshDir $binary
@@ -431,21 +414,38 @@ LogLevel DEBUG1
foreach($directory in $deniedToolDirs){Invoke-Bounded icacls.exe (@($directory.TrimEnd('\'),'/remove:d')+@($ownedAccounts | ForEach-Object {"*$($_.sid)"})) 120 | Out-Null}
Write-Stage 'cleanup-toolchain-acl-complete'
Write-Stage 'cleanup-user-profile-start'
$profileTargets=@(foreach($account in $ownedAccounts){
if($account.sid){@{sid=$account.sid;watch=$null;done=$false;profiles=@();waitMs=0;disposition=$null}}
})
$report.profileCleanup=@()
foreach($account in $ownedAccounts){
if(-not $account.sid){continue}
$profileWait=[Diagnostics.Stopwatch]::StartNew()
do {
$profiles=@(Get-CimInstance Win32_UserProfile | Where-Object SID -eq $account.sid)
if(-not @($profiles | Where-Object Loaded).Count){break}
Start-Sleep -Milliseconds 500
} while($profileWait.Elapsed.TotalSeconds -lt 30)
$report.profileUnloadWaitMs=$profileWait.ElapsedMilliseconds
$report.privateProfile=@($profiles | ForEach-Object {@{loaded=$_.Loaded;status=$_.Status}})
Write-Stage 'cleanup-user-profile-observed'
$loadedProfiles=@($profiles | Where-Object Loaded)
$report.profileCleanup+=if($loadedProfiles.Count){'Loaded profile retained for disposable CI VM destruction'}else{'Unloaded profile removed'}
$profiles | Where-Object {-not $_.Loaded} | Remove-CimInstance
$report.privateProfiles=@()
do {
foreach($entry in @($profileTargets | Where-Object {-not $_.done})){
if(-not $entry.watch){$entry.watch=[Diagnostics.Stopwatch]::StartNew()}
$profiles=@(Get-CimInstance Win32_UserProfile -Filter "SID='$($entry.sid)'")
if(@($profiles | Where-Object SID -ne $entry.sid).Count){throw 'Private profile query returned an unrelated SID'}
if(@($profiles | Where-Object Loaded).Count -and $entry.watch.ElapsedMilliseconds -lt 30000){continue}
# A profile may reload after the polling snapshot.
$profiles=@(Get-CimInstance Win32_UserProfile -Filter "SID='$($entry.sid)'")
if(@($profiles | Where-Object SID -ne $entry.sid).Count){throw 'Private profile query returned an unrelated SID'}
$loadedProfiles=@($profiles | Where-Object Loaded)
if($loadedProfiles.Count -and $entry.watch.ElapsedMilliseconds -lt 30000){continue}
$profiles | Where-Object {-not $_.Loaded} | Remove-CimInstance
$entry.profiles=@($profiles | ForEach-Object {@{loaded=$_.Loaded;status=$_.Status}})
$entry.waitMs=$entry.watch.ElapsedMilliseconds
$entry.disposition=if($loadedProfiles.Count){'Loaded profile retained for disposable CI VM destruction'}else{'Unloaded profile removed'}
$entry.done=$true
$report.profileCleanup=@($profileTargets | Where-Object done | ForEach-Object disposition)
$report.privateProfiles=@($profileTargets | Where-Object done | ForEach-Object {@{sid=$_.sid;waitMs=$_.waitMs;profiles=$_.profiles;disposition=$_.disposition}})
$report.profileUnloadWaitMs=$entry.waitMs
$report.privateProfile=$entry.profiles
Write-Stage 'cleanup-user-profile-observed'
}
if(@($profileTargets | Where-Object {-not $_.done}).Count){Start-Sleep -Milliseconds 500}
} while(@($profileTargets | Where-Object {-not $_.done}).Count)
if($profileTargets.Count){
$report.profileUnloadWaitMs=$profileTargets[-1].waitMs
$report.privateProfile=$profileTargets[-1].profiles
}
Write-Stage 'cleanup-user-profile-complete'
Write-Stage 'cleanup-user-start'
@@ -33,5 +33,166 @@ try {
if(($result.hidden -join '|') -ne "$nodeDir|$gccDir"){throw 'Toolchain PATH entries not hidden'}
if(($result.kept -join '|') -ne "$plainDir|$(Join-Path $pathRoot 'missing')|Q:\no-such-drive"){throw 'Plain PATH entries not kept in order'}
} finally {Remove-Item -LiteralPath $pathRoot -Recurse -Force -ErrorAction SilentlyContinue}
# Mock only the machine boundary; exercise the shared installer without servicing this host.
. (Join-Path $PSScriptRoot 'windows-ssh-capability.ps1')
function Assert-IsolatedWindowsSshCi([string]$Arch){if($script:refuseCapabilityHost){throw 'Injected host refusal'}}
function Assert-WindowsSshGlobalServerDormant([string]$Server){$script:globalChecks++;if($script:globalChecks -eq $script:refuseGlobalCheck){throw 'Injected global server refusal'}}
function Assert-WindowsSshStockShell {$script:shellChecks++;if($script:shellChecks -eq $script:refuseShellCheck){throw 'Injected shell refusal'}}
function Get-WindowsCapability([switch]$Online,[string]$Name){$script:capabilityQueries++;return @{State=$script:capabilityState}}
function Add-WindowsCapability([switch]$Online,[string]$Name){$script:capabilityAdds++;if($script:failCapabilityInstall){throw 'Injected capability install failure'}}
function Reset-CapabilityControl([string]$State){
$script:capabilityState=$State;$script:capabilityQueries=0;$script:capabilityAdds=0
$script:globalChecks=0;$script:shellChecks=0;$script:refuseGlobalCheck=0;$script:refuseShellCheck=0
$script:refuseCapabilityHost=$false;$script:failCapabilityInstall=$false
$script:capabilityStages=[Collections.Generic.List[string]]::new()
}
foreach($state in @('Installed','NotPresent')){
Reset-CapabilityControl $state
$capabilityReport=@{}
Install-WindowsInboxSshCapability 'x64' $capabilityReport {param($name) $script:capabilityStages.Add($name)}
$expectedAdds=if($state -eq 'Installed'){0}else{1}
if($capabilityReport.inboxCapabilityInitialState -ne $state -or $script:capabilityAdds -ne $expectedAdds -or $script:globalChecks -ne 2 -or $script:shellChecks -ne 2 -or ($script:capabilityStages -join ',') -ne 'inbox-capability-start,inbox-capability-complete'){throw 'Shared capability preparation did not preserve the install and guard boundaries'}
}
foreach($fault in @('host','global-before','shell-before','global-after','shell-after','install')){
Reset-CapabilityControl 'NotPresent'
switch($fault){
'host' {$script:refuseCapabilityHost=$true}
'global-before' {$script:refuseGlobalCheck=1}
'shell-before' {$script:refuseShellCheck=1}
'global-after' {$script:refuseGlobalCheck=2}
'shell-after' {$script:refuseShellCheck=2}
'install' {$script:failCapabilityInstall=$true}
}
$rejected=$false
try {Install-WindowsInboxSshCapability 'x64' @{} {param($name) $script:capabilityStages.Add($name)}} catch {$rejected=$true}
if(-not $rejected -or $script:capabilityStages.Contains('inbox-capability-complete')){throw "Capability fault did not fail closed: $fault"}
if($fault -in @('host','global-before','shell-before') -and $script:capabilityAdds){throw 'Capability mutation preceded its host guards'}
}
$capabilityRoot=Join-Path ([IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString('N'))
try {
New-Item -ItemType Directory -Path $capabilityRoot | Out-Null
$capabilityReceipt=Join-Path $capabilityRoot 'capability.json'
Reset-CapabilityControl 'Installed'
Initialize-WindowsInboxSshCapability 'x64' $capabilityReceipt
$completed=Get-Content -LiteralPath $capabilityReceipt -Raw | ConvertFrom-Json
if($completed.status -ne 'passed' -or $completed.inboxCapabilityInitialState -ne 'Installed'){throw 'Capability success receipt missing its observed initial state'}
Reset-CapabilityControl 'NotPresent';$script:failCapabilityInstall=$true
$rejected=$false
try {Initialize-WindowsInboxSshCapability 'x64' $capabilityReceipt} catch {$rejected=$true}
$failed=Get-Content -LiteralPath $capabilityReceipt -Raw | ConvertFrom-Json
if(-not $rejected -or $failed.status -ne 'failed' -or $failed.inboxCapabilityInitialState -ne 'NotPresent' -or $failed.error -ne 'Injected capability install failure'){throw 'Failed capability preparation masqueraded as a passing receipt'}
} finally {Remove-Item -LiteralPath $capabilityRoot -Recurse -Force -ErrorAction SilentlyContinue}
& {
param($ProofAst)
$cleanup=@($ProofAst.FindAll({param($node) $node -is [Management.Automation.Language.TryStatementAst] -and $node.Body.Extent.Text.Contains("Write-Stage 'cleanup-start'")},$true))
if($cleanup.Count -ne 1 -or -not $cleanup[0].Extent.Text.Contains('[Diagnostics.Stopwatch]::StartNew()')){throw 'Cleanup clock boundary missing'}
# Run the real cleanup gates with virtual clocks; no Windows machine operation may escape these mocks.
$cleanupBlock=[scriptblock]::Create($cleanup[0].Extent.Text.Replace('[Diagnostics.Stopwatch]::StartNew()','(New-ProfileControlStopwatch)').Replace('[DateTime]::UtcNow','(Get-ProfileControlUtcNow)'))
$ownedSids=@('S-1-5-21-100-200-300-1001','S-1-5-21-100-200-300-1002','S-1-5-21-100-200-300-1003')
$foreignSid='S-1-5-21-100-200-300-9000';$control=@{}
function New-ProfileControlStopwatch {
$watch=[pscustomobject]@{StartedMilliseconds=$control.clockMs;Control=$control}
$watch | Add-Member ScriptProperty ElapsedMilliseconds {$this.Control.clockMs-$this.StartedMilliseconds}
return $watch
}
function Get-ProfileControlUtcNow {[DateTime]::new(2026,10,2,0,0,0,[DateTimeKind]::Utc).AddMilliseconds($control.clockMs)}
function Start-Sleep([int]$Milliseconds,[int]$Seconds){$control.clockMs+=$Milliseconds+1000*$Seconds}
function Write-Stage([string]$Stage){$control.stages.Add($Stage)}
function Record-PrivateServiceDiagnostics([switch]$AfterStop){}
function Test-Path([string]$LiteralPath){if($LiteralPath -ne 'HKLM:\SOFTWARE\OpenSSH'){throw 'Unexpected filesystem query'};return $false}
function Get-ItemProperty([string]$LiteralPath,[object]$ErrorAction){if($LiteralPath -ne 'HKLM:\SOFTWARE\OpenSSH'){throw 'Unexpected registry query'};return $null}
function Remove-ItemProperty {throw 'Unexpected registry mutation'}
function Stop-Service([string]$Name,[switch]$Force,[object]$ErrorAction){if($Name -ne 'orca-sshd-control'){throw 'Foreign service stop'};if($control.case.Fault -ne 'server-exit'){$control.serverLive=$false}}
function Invoke-Bounded([string]$Program,[string[]]$Arguments){if($Program -ne 'sc.exe' -or ($Arguments -join '|') -ne 'delete|orca-sshd-control'){throw 'Unexpected native command'};if($control.case.Fault -ne 'service-absence'){$control.servicePresent=$false}}
function Get-Process([int]$Id,[object]$ErrorAction){if($Id -ne 100){throw 'Foreign process query'};if($control.serverLive){@{Id=100}}}
function Get-Service([string]$Name,[object]$ErrorAction){if($Name -ne 'orca-sshd-control'){throw 'Foreign service query'};if($control.servicePresent){@{Name=$Name}}}
function Get-ProfileControlLoaded([string]$Sid){
$state=$control.profiles[$Sid]
if($state.Mode -eq 'late'){return $control.clockMs -lt $state.UnloadAtMs}
if($state.Mode -in @('reload','reload-at-deadline')){return $state.CurrentLoaded}
return $state.Mode -eq 'loaded'
}
function Get-CimInstance([Parameter(Position=0)][string]$ClassName,[string]$Filter){
if($ClassName -eq 'Win32_Service'){
if($Filter -ne "Name='orca-sshd-control'"){throw 'Foreign service query'}
if($control.servicePresent){@{PathName=$(if($control.case.Fault -eq 'service-identity'){'C:\foreign\sshd.exe'}else{'C:\fake\ossh-control\sshd.exe'});ProcessId=$(if($control.case.Fault -eq 'service-pid'){200}else{100})}};return
}
if($ClassName -eq 'Win32_Process'){
if($control.case.Fault -eq 'child-exit'){@{ExecutablePath='C:\fake\OpenSSH\session.exe';ProcessId=101;ParentProcessId=100;CreationDate=(Get-ProfileControlUtcNow)}};return
}
if($ClassName -ne 'Win32_UserProfile' -or $Filter -notmatch "^SID='(S-1-5-21-100-200-300-\d+)'$" -or $Matches[1] -notin $ownedSids){throw 'Profile query must target an owned SID'}
$sid=$Matches[1];$control.clockMs+=$control.case.QueryCostMs;$control.profileQueries[$sid]++
if($control.case.Fault -eq 'query' -and $sid -eq $ownedSids[1]){throw 'Injected profile query failure'}
if($control.case.Fault -eq 'foreign-result' -and $sid -eq $ownedSids[1]){[pscustomobject]@{SID=$foreignSid;Loaded=$false;Status=0};return}
$state=$control.profiles[$sid]
if($state.Mode -eq 'absent' -or $state.Deleted){return}
$loaded=Get-ProfileControlLoaded $sid
if($state.Mode -eq 'reload' -and $control.profileQueries[$sid] -eq 1){$loaded=$false;$state.CurrentLoaded=$true}
if($state.Mode -eq 'reload-at-deadline' -and $control.clockMs -ge 30000 -and -not $state.Reinjected){$loaded=$false;$state.CurrentLoaded=$true;$state.Reinjected=$true}
[pscustomobject]@{SID=$sid;Loaded=$loaded;Status=0}
}
function Remove-CimInstance {
param([Parameter(ValueFromPipeline=$true)][object]$InputObject)
process {
if($InputObject.SID -notin $ownedSids -or $InputObject.Loaded -or (Get-ProfileControlLoaded $InputObject.SID)){throw 'Unsafe profile deletion attempted'}
if($control.case.Fault -eq 'delete'){throw 'Injected profile deletion failure'}
$control.profiles[$InputObject.SID].Deleted=$true;$control.removed.Add($InputObject.SID)
}
}
function Get-LocalUser([string]$Name,[object]$ErrorAction){if(-not $control.users.ContainsKey($Name)){throw 'Foreign account query'};if($control.users[$Name]){@{Name=$Name}}}
function Remove-LocalUser([string]$Name){if(-not $control.users.ContainsKey($Name)){throw 'Foreign account deletion'};if($control.case.Fault -ne 'account'){$control.users[$Name]=$false}}
function Remove-Item([string]$LiteralPath,[switch]$Recurse,[switch]$Force){if($LiteralPath -ne 'C:\fake\ossh-control'){throw 'Foreign filesystem deletion'};if($control.case.Fault -eq 'keys'){throw 'Injected private key deletion failure'};$control.keysRemoved=$true}
function Assert-ProfileCleanup([hashtable]$Case){
$control.Clear();$control.case=$Case;$control.clockMs=0;$control.serverLive=$true;$control.servicePresent=$true;$control.keysRemoved=$false
$control.stages=[Collections.Generic.List[string]]::new();$control.removed=[Collections.Generic.List[string]]::new()
$control.profiles=@{};$control.users=@{};$control.profileQueries=@{}
$ownedAccounts=@(for($index=0;$index -lt $ownedSids.Count;$index++){
$sid=$ownedSids[$index];$control.profileQueries[$sid]=0
$control.profiles[$sid]=@{Mode=$Case.Modes[$index];UnloadAtMs=$Case.UnloadAtMs[$index];CurrentLoaded=$true;Deleted=$false}
$name="orca-control-$index";$control.users[$name]=$true;@{name=$name;sid=$(if($Case.MissingSid -and $index -eq 2){$null}else{$sid})}
})
$report=@{status='proof-passed-cleanup-pending'};$openSshKey='HKLM:\SOFTWARE\OpenSSH';$serviceName='orca-sshd-control'
$root='C:\fake\ossh-control';$createdService=$true;$ownedServerPid=100;$sshDir='C:\fake\OpenSSH';$preexisting=@();$deniedToolDirs=@()
& $cleanupBlock
if($Case.Fault){
if($report.status -ne 'failed' -or $report.cleanupError -ne $Case.Error -or $control.keysRemoved -or $control.stages.Contains('cleanup-private-files-complete')){throw "Cleanup fault did not fail at its gate: $($Case.Name)"}
if($Case.Fault -notin @('account','keys') -and @($control.users.Values | Where-Object {-not $_}).Count){throw "Failure bypassed account cleanup gate: $($Case.Name)"}
return
}
if($report.status -ne 'passed' -or @($control.users.Values | Where-Object {$_}).Count -or -not $control.keysRemoved){throw "Cleanup omitted account/key gates: $($Case.Name)"}
if(($control.removed.ToArray() | Sort-Object) -join ',' -ne (($Case.Removed | Sort-Object) -join ',')){throw "Wrong removed SIDs: $($Case.Name)"}
if(@($report.profileCleanup | Where-Object {$_ -eq 'Loaded profile retained for disposable CI VM destruction'}).Count -ne $Case.Retained){throw "Wrong retained disposition: $($Case.Name)"}
foreach($sid in $Case.FullWindow){
$observed=@($report.privateProfiles | Where-Object sid -eq $sid)
if($observed.Count -ne 1 -or $observed[0].waitMs -lt 30000){throw "Short profile observation window: $($Case.Name)"}
}
if($control.clockMs -gt $Case.MaxClockMs){throw "Profile windows were serialized: $($Case.Name)"}
if($Case.MissingSid -and $control.profileQueries[$ownedSids[2]]){throw 'Missing SID gained profile deletion authority'}
if($report.privateProfiles.Count -and ($report.profileUnloadWaitMs -ne $report.privateProfiles[-1].waitMs -or ($report.privateProfile | ConvertTo-Json -Compress) -ne ($report.privateProfiles[-1].profiles | ConvertTo-Json -Compress))){throw 'Legacy scalar observation lost owned-account order'}
}
$cases=@(
@{Name='three loaded full windows';Modes=@('loaded','loaded','loaded');Retained=3;Removed=@();FullWindow=$ownedSids},
@{Name='unload at 29999ms';Modes=@('late','unloaded','absent');UnloadAtMs=@(29999,0,0);Retained=0;Removed=$ownedSids[0..1];FullWindow=@($ownedSids[0])},
@{Name='reload before deletion';Modes=@('reload','unloaded','absent');Retained=1;Removed=@($ownedSids[1]);FullWindow=@($ownedSids[0])},
@{Name='independent unloads';Modes=@('late','loaded','late');UnloadAtMs=@(5000,0,20000);Retained=1;Removed=@($ownedSids[0],$ownedSids[2]);FullWindow=@($ownedSids[1])},
@{Name='slow provider queries';Modes=@('loaded','loaded','loaded');QueryCostMs=200;Retained=3;Removed=@();FullWindow=$ownedSids;MaxClockMs=40000},
@{Name='reload at expired window';Modes=@('reload-at-deadline','unloaded','absent');Retained=1;Removed=@($ownedSids[1]);FullWindow=@($ownedSids[0])},
@{Name='late unload honestly retained';Modes=@('loaded','loaded','late');UnloadAtMs=@(0,0,45000);Retained=3;Removed=@();FullWindow=$ownedSids},
@{Name='missing SID is skipped';Modes=@('unloaded','absent','unloaded');Retained=0;Removed=@($ownedSids[0]);MissingSid=$true}
)
foreach($case in $cases){
if(-not $case.UnloadAtMs){$case.UnloadAtMs=@(0,0,0)}
if(-not $case.MaxClockMs){$case.MaxClockMs=33000}
Assert-ProfileCleanup $case
}
$failures=@{
query='Injected profile query failure';delete='Injected profile deletion failure';'foreign-result'='Private profile query returned an unrelated SID'
'service-identity'='Private service identity changed; refuse stop';'service-pid'='Private service identity changed; refuse stop'
'server-exit'='Private sshd process still live; no PID-only kill attempted'
'service-absence'='Private service still registered';'child-exit'='Private SSH child processes remain; preserve files and discard ephemeral runner'
account='Private account still exists';keys='Injected private key deletion failure'
}
foreach($failure in $failures.GetEnumerator()){Assert-ProfileCleanup @{Name=$failure.Key;Fault=$failure.Key;Error=$failure.Value;Modes=@('unloaded','unloaded','unloaded');UnloadAtMs=@(0,0,0)}}
} $ast
# Extract functions through the AST: never provision the fixture while testing diagnostics.
'PASS: fixture parse, five numeric-diagnostic cases and the toolchain PATH split'
'PASS: fixture parse, diagnostics, PATH split, capability boundaries, profile windows and cleanup failure gates'
@@ -0,0 +1,61 @@
function Assert-IsolatedWindowsSshCi([ValidateSet('arm64','x64')][string]$Arch) {
$os=@{arm64='Arm64';x64='X64'}[$Arch]
if($env:GITHUB_ACTIONS -ne 'true' -or $env:ORCA_ISOLATED_SSH_CI -ne '1' -or [Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString() -ne $os){throw "Requires isolated native $Arch GitHub runner"}
$admin=([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if(-not $admin){throw 'Administrative private service/account setup required'}
}
function Assert-WindowsSshGlobalServerDormant([ValidateSet('preview','inbox')][string]$Server) {
$global=Get-CimInstance Win32_Service -Filter "Name='sshd'"
if(-not $global){return}
$inboxDir=Join-Path $env:WINDIR 'System32\OpenSSH'
# Inbox installation may register the global service; it must never be started here.
if($Server -ne 'inbox' -or $global.State -ne 'Stopped' -or $global.PathName.Trim('"') -ne (Join-Path $inboxDir 'sshd.exe')){throw 'Refuse an existing global SSH server'}
}
function Assert-WindowsSshStockShell {
$registry=Get-ItemProperty -LiteralPath 'HKLM:\SOFTWARE\OpenSSH' -ErrorAction SilentlyContinue
if($registry.DefaultShell -or $registry.DefaultShellCommandOption){throw 'Requires stock cmd.exe OpenSSH shell at start'}
}
function Write-WindowsSshReceiptStage([hashtable]$Report,[string]$Receipt,[string]$Stage) {
$timestamp=[DateTime]::UtcNow.ToString('o')
$Report.stages+=@{stage=$Stage;utc=$timestamp}
try {
$bytes=[Text.UTF8Encoding]::new($false).GetBytes(($Report | ConvertTo-Json -Depth 6))
$temporary="$Receipt.pending"
$stream=[IO.FileStream]::new($temporary,[IO.FileMode]::Create,[IO.FileAccess]::Write,[IO.FileShare]::Read)
try {$stream.Write($bytes,0,$bytes.Length);$stream.Flush($true)} finally {$stream.Dispose()}
[IO.File]::Move($temporary,$Receipt,$true)
$written=$true
} catch {$written=$false;Write-Warning 'Progress receipt could not be updated; cleanup must still run'}
Write-Host "Native SSH stage: $Stage ($timestamp)"
return $written
}
function Install-WindowsInboxSshCapability([ValidateSet('arm64','x64')][string]$Arch,[hashtable]$Report,[scriptblock]$Stage) {
Assert-IsolatedWindowsSshCi $Arch
Assert-WindowsSshGlobalServerDormant 'inbox'
Assert-WindowsSshStockShell
& $Stage 'inbox-capability-start'
$capability=Get-WindowsCapability -Online -Name 'OpenSSH.Server~~~~0.0.1.0'
$Report.inboxCapabilityInitialState=[string]$capability.State
if($capability.State -ne 'Installed'){Add-WindowsCapability -Online -Name 'OpenSSH.Server~~~~0.0.1.0' | Out-Null}
Assert-WindowsSshGlobalServerDormant 'inbox'
Assert-WindowsSshStockShell
& $Stage 'inbox-capability-complete'
}
function Initialize-WindowsInboxSshCapability([ValidateSet('arm64','x64')][string]$Arch,[string]$Receipt) {
$report=@{scope='Windows inbox OpenSSH.Server capability preparation only';arch=$Arch;sourceSha=$env:GITHUB_SHA;runId=$env:GITHUB_RUN_ID;runAttempt=$env:GITHUB_RUN_ATTEMPT;runnerName=$env:RUNNER_NAME;imageVersion=$env:ImageVersion;status='running';stages=@();globalBootstrapCleanup='Disposable CI VM destruction is the boundary; no global sshd is started'}
try {
if(-not (Write-WindowsSshReceiptStage $report $Receipt 'preparation-start')){throw 'Initial progress receipt unavailable; refuse provisioning'}
Install-WindowsInboxSshCapability $Arch $report {param($name)
if(-not (Write-WindowsSshReceiptStage $report $Receipt $name)){throw 'Capability preparation progress receipt unavailable'}
}
$report.status='passed'
} catch {$report.status='failed';$report.error=$_.Exception.Message;throw}
finally {
if(-not (Write-WindowsSshReceiptStage $report $Receipt 'preparation-finished')){throw 'Final capability preparation receipt unavailable'}
}
}
@@ -13,6 +13,14 @@ const workflow = parse(
)
const job = workflow.jobs.hosts
const runStep = job.steps.find((step) => step.name?.startsWith('Run the Windows host cells'))
const provisioning = readFileSync(
join(projectDir, 'config/ci/windows-ssh-provider/preview-ssh/prove-preview-openssh.ps1'),
'utf8'
)
const capability = readFileSync(
join(projectDir, 'config/ci/windows-ssh-provider/preview-ssh/windows-ssh-capability.ps1'),
'utf8'
)
const manifest = (arch) =>
JSON.parse(
readFileSync(
@@ -47,6 +55,99 @@ describe('SSH Windows-host workflow', () => {
'x64/windows-2022/preview'
])
expect(job.env).toMatchObject({ ORCA_BACKGROUND_LAUNCH: '1', ORCA_ISOLATED_SSH_CI: '1' })
expect(job.strategy['fail-fast']).toBe(false)
expect(job['timeout-minutes']).toBe(75)
expect(runStep['timeout-minutes']).toBe(50)
})
it('overlaps only guarded ARM inbox capability preparation with the existing builds', () => {
const selfTestIndex = job.steps.findIndex((step) => step.name?.startsWith('Self-test'))
const prepareIndex = job.steps.findIndex((step) => step.id === 'inbox-capability')
const installIndex = job.steps.findIndex(
(step) => step.uses === './.github/actions/install-node-dependencies'
)
const buildIndex = job.steps.findIndex((step) => step.name?.startsWith('Build this runner'))
const prebuildIndex = job.steps.findIndex(
(step) => step.uses === './.github/actions/prepare-orcad-prebuilds'
)
const templateIndex = job.steps.findIndex((step) => step.name?.startsWith('Build the win32'))
const waitIndex = job.steps.findIndex((step) => step.wait === 'inbox-capability')
const runIndex = job.steps.indexOf(runStep)
expect([
selfTestIndex,
prepareIndex,
installIndex,
buildIndex,
prebuildIndex,
templateIndex,
waitIndex,
runIndex
]).toEqual([1, 2, 3, 4, 5, 6, 7, 8])
expect(job.steps[prepareIndex]).toMatchObject({
background: true,
shell: 'pwsh'
})
expect(job.steps[prepareIndex].if).toBeUndefined()
expect(job.steps[waitIndex].if).toBeUndefined()
expect(job.steps[prepareIndex].run.trim()).toMatch(
/^if\('\$\{\{ matrix\.server }}' -eq 'inbox' -and '\$\{\{ matrix\.arch }}' -eq 'arm64'\)\{[\s\S]+\}$/
)
expect(job.steps[prepareIndex].run).toContain('Initialize-WindowsInboxSshCapability')
expect(job.steps[prepareIndex].run).toContain('inbox-capability-preparation.json')
expect(runStep.run).toContain('-InboxPreparationReceipt $preparation')
expect(runStep.run).toContain(
"$preparation=Join-Path $receipts 'inbox-capability-preparation.json'"
)
expect(runStep.run).toContain(
"if('${{ matrix.server }}' -eq 'inbox' -and '${{ matrix.arch }}' -eq 'arm64'){$preparation="
)
expect(runStep.background).toBeUndefined()
expect(job.steps.at(-1)).toMatchObject({ if: 'always()', uses: 'actions/upload-artifact@v7' })
})
it('shares one capability installer without bypassing native verification or private cleanup', () => {
expect(capability.match(/Add-WindowsCapability -Online/g)).toHaveLength(1)
expect(provisioning).not.toContain('Add-WindowsCapability')
expect(provisioning).toContain(". (Join-Path $PSScriptRoot 'windows-ssh-capability.ps1')")
expect(provisioning).toContain('Install-WindowsInboxSshCapability $Arch $report')
const install = capability.slice(
capability.indexOf('function Install-WindowsInboxSshCapability'),
capability.indexOf('function Initialize-WindowsInboxSshCapability')
)
const mutation = install.indexOf('Add-WindowsCapability')
expect(install.indexOf('Assert-IsolatedWindowsSshCi')).toBeLessThan(mutation)
expect(install.indexOf('Assert-WindowsSshGlobalServerDormant')).toBeLessThan(mutation)
expect(install.lastIndexOf('Assert-WindowsSshGlobalServerDormant')).toBeGreaterThan(mutation)
expect(install.indexOf('Assert-WindowsSshStockShell')).toBeLessThan(mutation)
expect(install.lastIndexOf('Assert-WindowsSshStockShell')).toBeGreaterThan(mutation)
for (const check of [
'(Machine $path) -ne $target.machine',
'Get-AuthenticodeSignature -LiteralPath $path',
'Inbox native input Microsoft signature invalid',
"Write-Stage 'host-cell-probe-start'",
"Write-Stage 'cleanup-default-shell-start'",
"Write-Stage 'cleanup-service-stop-delete-start'"
]) {
expect(provisioning).toContain(check)
}
})
it('keeps preparation provenance separate from the oracle current capability state', () => {
for (const [field, environment] of [
['sourceSha', 'GITHUB_SHA'],
['runId', 'GITHUB_RUN_ID'],
['runAttempt', 'GITHUB_RUN_ATTEMPT'],
['runnerName', 'RUNNER_NAME'],
['imageVersion', 'ImageVersion']
]) {
expect(capability).toContain(`${field}=$env:${environment}`)
expect(provisioning).toContain(`$preparation.${field} -ne $env:${environment}`)
}
expect(provisioning).toContain("$preparation.status -ne 'passed'")
expect(provisioning).toContain('$preparation.arch -ne $Arch')
expect(provisioning).toContain('$report.inboxCapabilityPreparation=$preparation')
expect(capability).toContain('$Report.inboxCapabilityInitialState=[string]$capability.State')
expect(capability).toContain("$report.status='failed';$report.error=$_.Exception.Message;throw")
})
it('fetches the preview release its hash manifests pin', () => {
+91
View File
@@ -1392,3 +1392,94 @@ one CI failure does not establish a failure-rate reduction.
The bounded 50-head main sample ending at 8ff6296 contained no root package
metadata changes. Removing app-version metadata from the Windows server cache
key would not improve reuse in that sample, so the key remains unchanged.
## Windows ARM SSH: prepare the inbox capability during independent builds
The ARM inbox lane starts guarded Windows capability preparation after the pure
provisioning self-test and waits for it before any private SSH server or host cell
runs. Dependency installation and the unchanged native artifacts can run during
that preparation. Preview and x64 lanes keep their existing serial provisioning;
the registered background step completes without mutation in those lanes.
The preparation and the foreground provider use the same installer and isolation
guards. The receipt must match the source, run, attempt, runner, image and native
architecture. The foreground provider still reads the installed capability and
verifies every native binary and Microsoft signature. Account ownership, ACLs,
DefaultShell, private service identity, host cells and cleanup remain independent
checks. A background failure propagates through the unconditional native wait.
Two full four-lane pairs used frozen source refs and the same dependency and
native-install policy. The [first baseline](https://github.com/stablyai/orca/actions/runs/36986929163)
ran before the [first candidate](https://github.com/stablyai/orca/actions/runs/36986970976);
the [second candidate](https://github.com/stablyai/orca/actions/runs/36991232037)
was dispatched before the [second baseline](https://github.com/stablyai/orca/actions/runs/36991234729).
Runner image versions matched within each platform in both pairs.
| Active job, seconds | First baseline | First candidate | Second baseline | Second candidate |
| ------------------- | -------------: | --------------: | --------------: | ---------------: |
| ARM inbox | 2,403 | 1,644 | 2,353 | 1,667 |
| ARM preview | 1,002 | 935 | 886 | 872 |
| x64 inbox | 636 | 732 | 616 | 620 |
| x64 preview | 562 | 561 | 623 | 566 |
The ARM inbox observations improved by 759 and 686 seconds. Baseline dependency
installation and artifact builds consumed 501 and 498 seconds before capability
installation could start. Candidate capability installation ran during that
work, but also took about 261 and 232 seconds less than the baseline. Candidate
dependency installation was slower, particularly in the second pair. These
observations support overlap on ARM; they do not establish a guaranteed 11–13
minute saving, a reduction in queue time, or the cause of installer variability.
The x64 lane showed no repeatable gain, so it keeps serial preparation.
All 16 actual Windows providers and 48 host-cell verdicts passed across the two
pairs. Receipts verify native machine identity, private service absence, owned
process exit, account removal and key removal. Loaded profile disposition remains
separate from those required cleanup checks. Hosted execution also verified the
native background/wait syntax; older actionlint versions do not recognize it.
### Overlap the private profile observation budgets
After service deletion and owned process exit, profile cleanup polls each owned
SID with its own full 30-second monotonic budget. Independent budgets now run
together. Every deletion follows a fresh targeted read; loaded profiles remain
for disposable VM destruction. Service identity, PID ownership, process exit,
account removal and key removal still fail the complete provider on error.
The maintained diagnostics self-test executes the actual cleanup try/catch with
scoped Windows API and clock controls. Eight positive cases cover full windows,
late unload, reload, query overhead, mixed states and missing SIDs; ten specific
failure cases cover foreign profiles and the required cleanup gates. Disposable
shortened-deadline and stale-snapshot mutations fail those controls. A separate
mocked real-clock observation took 30.179 seconds for three loaded profiles,
compared with about 90 seconds for serial full budgets. This measures polling,
not an actual Windows provider or the entire job.
The third profile no longer gains incidental extra time while earlier profiles
consume their budgets. A profile unloading at 45 seconds may therefore remain
where serial cleanup removed it. This uses the existing disposable-VM fallback;
it does not remove a loaded profile or relax mandatory account/key cleanup.
Hosted qualification of the combined workflow remains pending.
## Coordinator mail tests: advance observation windows without removing them
Six cases advance their original six 1,500 ms and ten 100 ms observation windows
with a scoped clock. Real filesystem, SQLite, journal, RPC and runtime work still
finishes asynchronously. The original journal-read gate and all counter and
operation assertions remain. Cancellation during delayed startup and the
Date-only age case retain real timers. Teardown stops the host and closes the
database before advancing the known 2,000 ms orphan repair, then asserts no fake
timers remain and restores the clock in `finally`.
Two opposite-order local pairs passed the same 23 cases and unchanged source
hashes. Selected-case totals fell from 13.674 to 3.318 seconds and from 13.276 to
6.323 seconds. Whole-file test totals fell from 24.845 to 10.829 seconds and from
21.500 to 19.410 seconds. Process wall times were 41.488/37.810 seconds and
42.140/78.450 seconds; the reverse candidate spent 56.31 seconds importing under
unrelated local load. Overall wall-time savings remain inconclusive.
Injected extra deliveries at 1,499 ms and 99 ms still fail the original assertions
in both clock modes. The latter candidate fails the unchanged journal-read gate
with the same extra provider start. A separate control confirms the orphan repair
actually executes against the closed database and leaves no fake timers. The
change retains all 121 original expectation sites and adds one teardown check;
it does not shorten the runtime's observation interval or claim a whole-PR gain.
@@ -34,6 +34,7 @@ import {
ensureStructuredAgentSessionHost,
stopStructuredAgentSessionRuntime
} from './structured-agent-session-runtime'
import { createCoordinatorMailObservationClock } from './structured-chat-coordinator-observation-clock.test-fixture'
import {
attachParams,
fakeCodex,
@@ -56,6 +57,7 @@ let db: OrchestrationDb
let host: StructuredAgentSessionHost
let dispatcher: RpcDispatcher
let requests = 0
const observationClock = createCoordinatorMailObservationClock(() => host, COORDINATOR)
function request(
method: string,
@@ -284,10 +286,15 @@ function startRuntime(): OrcaRuntimeService {
}
afterEach(async () => {
await stopStructuredAgentSessionRuntime()
db.close()
vi.restoreAllMocks()
await rm(root, { recursive: true, force: true })
try {
await stopStructuredAgentSessionRuntime()
db.close()
await observationClock.drainClosedDatabaseRepair()
vi.restoreAllMocks()
await rm(root, { recursive: true, force: true })
} finally {
observationClock.restore()
}
})
// Pointers are sent on asynchronous edges; the default 1s wait is too tight under a loaded parallel run.
@@ -350,15 +357,7 @@ describe('a worker result reaches the structured chat that coordinates it', () =
})
/** Fires both edges and waits until every gate read they started has answered. */
async function edgesAnswered(): Promise<void> {
const reads = vi.spyOn(host, 'journalSnapshot')
runtime.onStructuredSessionStatusForMail({ sessionId: COORDINATOR, status: null })
runtime.onStructuredSessionStatusForMail({ sessionId: COORDINATOR, status: 'idle' })
await vi.waitFor(() => expect(reads).toHaveBeenCalled(), WAIT)
await Promise.all(reads.mock.results.map((read) => read.value))
await new Promise((resolve) => setImmediate(resolve))
reads.mockRestore()
}
const edgesAnswered = (): Promise<void> => observationClock.edgesAnswered(runtime, WAIT)
/** The operation ids the coordinator's journal recorded for its pointer turns. */
async function pointerSends(): Promise<string[]> {
@@ -407,6 +406,7 @@ describe('a worker result reaches the structured chat that coordinates it', () =
})
it('does not restart a provider that dies before every echo, however many edges follow', async () => {
observationClock.start()
// What this pins: each death's own status edge used to re-point the mail, and that send started
// the provider again, about once a second for as long as the mail was unread.
await openChat(COORDINATOR)
@@ -416,7 +416,7 @@ describe('a worker result reaches the structured chat that coordinates it', () =
await finishWorker(taskId)
await vi.waitFor(() => expect(providerFaults.turnStarts).toBe(1), WAIT)
// A fixed window, not a poll: a respawn loop would restart it several times in it.
await new Promise((resolve) => setTimeout(resolve, 1_500))
await observationClock.observe(1_500)
await edgesAnswered()
expect(codex.connections.length - before).toBe(0)
expect(providerFaults.turnStarts).toBe(1)
@@ -426,6 +426,7 @@ describe('a worker result reaches the structured chat that coordinates it', () =
it.each(['exit-then-throw', 'throw-then-exit'] as const)(
'does not restart a provider that crashed while taking the pointer turn (%s)',
async (crash) => {
observationClock.start()
// The crash settles the send `unknown` with the connection's own error, not as a provider
// exit; every status edge after it re-pointed the mail and started the provider again.
await openChat(COORDINATOR)
@@ -434,7 +435,7 @@ describe('a worker result reaches the structured chat that coordinates it', () =
const before = providerFaults.starts
await finishWorker(taskId)
await vi.waitFor(() => expect(providerFaults.turnStarts).toBe(1), WAIT)
await new Promise((resolve) => setTimeout(resolve, 1_500))
await observationClock.observe(1_500)
await edgesAnswered()
expect(providerFaults.starts - before).toBe(0)
expect(providerFaults.turnStarts).toBe(1)
@@ -501,7 +502,7 @@ describe('a worker result reaches the structured chat that coordinates it', () =
expect(cancelled).toMatchObject({ ok: true })
providerFaults.startDelayMs = 0
// A fixed window, not a poll: a re-point would start the agent again in it.
await new Promise((resolve) => setTimeout(resolve, 1_500))
await observationClock.observe(1_500)
expect(providerFaults.starts - before).toBe(1)
expect(await pointerSends()).toHaveLength(1)
await edgesAnswered()
@@ -511,6 +512,7 @@ describe('a worker result reaches the structured chat that coordinates it', () =
})
it('points a held pointer once more after Orca restarts, under a new id', async () => {
observationClock.start()
await openChat(COORDINATOR)
const { runId, taskId } = await coordinatorRunAndTask()
providerFaults.dieBeforeEveryEcho = true
@@ -525,7 +527,7 @@ describe('a worker result reaches the structured chat that coordinates it', () =
dispatcher = new RpcDispatcher({ runtime, methods: ORCHESTRATION_METHODS })
const before = providerFaults.starts
await vi.waitFor(() => expect(providerFaults.turnStarts).toBe(2), WAIT)
await new Promise((resolve) => setTimeout(resolve, 1_500))
await observationClock.observe(1_500)
await edgesAnswered()
expect(providerFaults.starts - before).toBe(1)
expect(providerFaults.turnStarts).toBe(2)
@@ -539,6 +541,7 @@ describe('a worker result reaches the structured chat that coordinates it', () =
async function refusedStartsFor(
refusal: () => Error
): Promise<{ runId: string; starts: number }> {
observationClock.start()
await openChat(COORDINATOR)
const { runId, taskId } = await coordinatorRunAndTask()
await host.close(COORDINATOR, 'evict')
@@ -551,7 +554,7 @@ describe('a worker result reaches the structured chat that coordinates it', () =
WAIT
)
// A fixed window, not a poll: a retry loop would start it several times in it.
await new Promise((resolve) => setTimeout(resolve, 1_500))
await observationClock.observe(1_500)
return { runId, starts: providerFaults.starts - before }
}
@@ -576,7 +579,7 @@ describe('a worker result reaches the structured chat that coordinates it', () =
const before = providerFaults.starts
for (let edge = 0; edge < 5; edge += 1) {
runtime.onStructuredSessionStatusForMail({ sessionId: COORDINATOR, status: 'idle' })
await new Promise((resolve) => setTimeout(resolve, 100))
await observationClock.observe(100)
}
await edgesAnswered()
expect(providerFaults.starts).toBe(before)
@@ -0,0 +1,55 @@
import { expect, vi } from 'vitest'
import type { StructuredAgentSessionHost } from '../native-chat/agent-session-wire/structured-agent-session-host'
import type { OrcaRuntimeService } from './orca-runtime'
import { waitForStructuredAgentSessionRecovery } from './structured-agent-session-runtime'
export function createCoordinatorMailObservationClock(
getHost: () => StructuredAgentSessionHost,
sessionId: string
) {
let active = false
return {
start(): void {
vi.useFakeTimers({ toFake: ['Date', 'setTimeout', 'clearTimeout'] })
active = true
},
async observe(ms: number): Promise<void> {
if (!active) {
await new Promise((resolve) => setTimeout(resolve, ms))
return
}
await vi.advanceTimersByTimeAsync(ms)
await waitForStructuredAgentSessionRecovery()
await getHost().flushStreamedEvents(sessionId)
await new Promise<void>((resolve) => setImmediate(resolve))
},
async edgesAnswered(
runtime: Pick<OrcaRuntimeService, 'onStructuredSessionStatusForMail'>,
wait: { timeout: number }
): Promise<void> {
const reads = vi.spyOn(getHost(), 'journalSnapshot')
runtime.onStructuredSessionStatusForMail({ sessionId, status: null })
runtime.onStructuredSessionStatusForMail({ sessionId, status: 'idle' })
await vi.waitFor(() => expect(reads).toHaveBeenCalled(), wait)
await Promise.all(reads.mock.results.map((read) => read.value))
await new Promise((resolve) => setImmediate(resolve))
reads.mockRestore()
},
async drainClosedDatabaseRepair(): Promise<void> {
if (!active) {
return
}
// The runtime's orphan mailbox repair must still run against the closed database.
await vi.advanceTimersByTimeAsync(2_000)
await waitForStructuredAgentSessionRecovery()
await new Promise<void>((resolve) => setImmediate(resolve))
expect(vi.getTimerCount()).toBe(0)
},
restore(): void {
if (active) {
vi.useRealTimers()
}
active = false
}
}
}